feat: created product image model

This commit is contained in:
2025-02-18 15:54:02 +01:00
parent cdaeec468a
commit fe4b783658
27 changed files with 644 additions and 563 deletions
-1
View File
@@ -37,7 +37,6 @@ THIRD_PARTY_APPS = [
]
PROJECT_APPS = [
"files",
"shop",
"theme",
"users",
View File
-12
View File
@@ -1,12 +0,0 @@
from django.contrib import admin
from files.models import FileUpload
# Register your models here.
@admin.register(FileUpload)
class FileUploadAdmin(admin.ModelAdmin):
list_display = (
"id",
"file",
)
View File
View File
-16
View File
@@ -1,16 +0,0 @@
from rest_framework import serializers
from files.models import FileUpload
class FileUploadSerializer(serializers.ModelSerializer):
class Meta:
model = FileUpload
fields = ("file", "id")
read_only_fields = ("id",)
class NoIDFileUploadSerializer(serializers.ModelSerializer):
class Meta:
model = FileUpload
fields = ("file",)
-11
View File
@@ -1,11 +0,0 @@
from rest_framework.routers import DefaultRouter
from files.api.v1.views import FileUploadViewSet
app_name = "files"
router = DefaultRouter(trailing_slash=True)
router.register("", FileUploadViewSet)
urlpatterns = router.urls
-9
View File
@@ -1,9 +0,0 @@
from rest_framework.viewsets import ModelViewSet
from files.api.v1.serializers import FileUploadSerializer
from files.models import FileUpload
class FileUploadViewSet(ModelViewSet):
serializer_class = FileUploadSerializer
queryset = FileUpload.objects.all()
-6
View File
@@ -1,6 +0,0 @@
from django.apps import AppConfig
class FilesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "files"
-28
View File
@@ -1,28 +0,0 @@
# Generated by Django 5.0.3 on 2024-05-04 22:12
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="FileUpload",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("file", models.FileField(upload_to="uploads")),
],
),
]
View File
-8
View File
@@ -1,8 +0,0 @@
from django.db import models
class FileUpload(models.Model):
file = models.FileField(upload_to="uploads", blank=False, null=False)
def __str__(self):
return self.file.url
-1
View File
@@ -1 +0,0 @@
# Create your tests here.
-1
View File
@@ -1 +0,0 @@
# Create your views here.
+9
View File
@@ -12,6 +12,7 @@ from shop.models import (
ProductBatch,
ProductCategory,
ProductPrice,
ProductImage,
Provider,
ShippingMethod,
ShopSettings,
@@ -166,3 +167,11 @@ class ShopSettingsAdmin(ModelAdmin):
"currency_code",
"terminal",
)
@admin.register(ProductImage)
class ProductImageAdmin(ModelAdmin):
list_display = (
"id",
"original",
)
+473 -11
View File
@@ -1,10 +1,10 @@
# Generated by Django 5.1.3 on 2025-01-03 11:17
import uuid
from decimal import Decimal
# Generated by Django 5.1.4 on 2025-02-18 13:32
import django.db.models.deletion
import django.utils.timezone
import shop.models
import uuid
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
@@ -14,7 +14,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
("files", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@@ -115,6 +114,258 @@ class Migration(migrations.Migration):
"verbose_name_plural": "proveedores",
},
),
migrations.CreateModel(
name="ShopSettings",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"merchant_code",
models.CharField(
max_length=9, verbose_name="Identificación de comercio"
),
),
(
"currency_code",
models.CharField(
choices=[
("8", "LEK ALL"),
("12", "ALGERIAN DINAR DZD"),
("24", "ANGOLA KWANZA AOK"),
("30", "PROBANDO DESA MON"),
("31", "AZERBAIJANIAN MANAT AZM"),
("32", "ARGENTINE AUSTRAL ARP"),
("36", "AUSTRALIAN DOLLAR AUD"),
("44", "BAHAMIAN DOLLAR BSD"),
("48", "BAHRAINI DINAR BHD"),
("50", "TAKA BDT"),
("51", "ARMENIAN DRAM AMD"),
("52", "BARBADOS DOLLAR BBD"),
("60", "BERMUDAN DOLLAR BMD"),
("64", "NGULTRUM BTN"),
("68", "BOLIVIAN PESO BOP"),
("70", "DINAR BAD"),
("72", "PULA BWP"),
("76", "CRUZEIRO BRC"),
("84", "BELIZE DOLLAR 084"),
("90", "SOLOMON ISLANDS DOLL SBD"),
("96", "BRUNEI DOLLAR BND"),
("100", "LEV BGL"),
("104", "KYAT BUK"),
("108", "BURUNDI FRANC BIF"),
("112", "BELARUSSIAN RUBLE BYB"),
("116", "RIEL KHR"),
("124", "CANADIAN DOLLAR CAD"),
("132", "CAPE VERDE ESCUDO CVE"),
("136", "CAYMAN ISLANDS DOLLA KYD"),
("144", "SRI LANKA RUPEE LKR"),
("152", "CHILEAN PESO CLP"),
("156", "YUAN RENMINBI CNY"),
("157", "CHINESE RENMIMBI CNH"),
("158", "CHINESE RENMINBI CNX"),
("170", "COLOMBIAN PESO COP"),
("174", "COMOROS FRANC KMF"),
("180", "ZAIRE ZRZ"),
("188", "COSTA RICA COLON CRC"),
("191", "CROATIAN KUNA HRK"),
("192", "CUBAN PESO CUP"),
("196", "CYPRUS POUND CYP"),
("200", "KORUNA CSK"),
("203", "CZECH KORUNA CZK"),
("208", "DANISH KRONE DKK"),
("214", "DOMINICAN PESO DOP"),
("218", "SUCRE ECS"),
("222", "EL SALVADOR COLON SVC"),
("226", "EKWELE GQE"),
("230", "ETHIOPIAN BIRR ETB"),
("232", "ERITREAN NAKTAN ERN"),
("233", "ESTONIAN KROON EEK"),
("238", "FALKLAND ISLANDS FKP"),
("242", "FIJI DOLLAR FJD"),
("262", "DJIBOUTI FRANC DJF"),
("268", "GEORGIAN LARI GEL"),
("270", "DALASI GMD"),
("278", "MARK DER DDR DDM"),
("288", "GHANA CEDI GHC"),
("292", "GIBRALTAR POUND GIP"),
("320", "QUETZAL GTQ"),
("324", "SYLI GNS"),
("328", "GUYANA DOLLAR GYD"),
("332", "GOURDE HTG"),
("340", "LEMPIRA HNL"),
("344", "HONG KONG DOLLAR HKD"),
("348", "FORINT HUF"),
("352", "ICELAND KRONA ISK"),
("356", "INDIAN RUPEE INR"),
("360", "RUPIAH IDR"),
("364", "IRANIAL RIAL IRR"),
("365", "IRANIAN AIRLINE RATE IRA"),
("368", "IRAQI DINAR IQD"),
("376", "ISRAEL SHEKEL ILS"),
("388", "JAMAICAN DOLLAR JMD"),
("392", "YEN JPY"),
("398", "TENGE KZT"),
("400", "JORDANIAN DINAR JOD"),
("404", "KENYAN SHILLING KES"),
("408", "NORTH KOREAN WON KPW"),
("410", "KOREAN WON KRW"),
("414", "KUWAITI DINAR KWD"),
("417", "KYRGYZSTAN SON KGS"),
("418", "KIP LAK"),
("422", "LEBANESE POUND LBP"),
("426", "LESOTHO LOTI LSM"),
("428", "LATVIAN LAT LVL"),
("430", "LIBERIAN DOLLAR LRD"),
("434", "LIBYAN DINAR LYD"),
("440", "LITHUANIAN LITAS LTL"),
("446", "PATACA MOP"),
("450", "MALAGASY FRANC MGF"),
("454", "MALAWI KWACHA MWK"),
("458", "MALASYAN RINGGIT MYR"),
("462", "MALDIVE RUPEE MVR"),
("466", "MALI MLF"),
("470", "MALTESE LIRA MTL"),
("478", "OUGUIYA MRO"),
("480", "MAURITIUS RUPEE MUR"),
("484", "MEXICAN PESO MXP"),
("496", "TUGRIK MNT"),
("498", "MOLDOVIAN LEU MDL"),
("504", "MORROCAN DIRHAM MAD"),
("508", "METICAL MZM"),
("512", "RIAL OMANI OMR"),
("516", "NAMIBIAN DOLLAR NAD"),
("524", "NEPALESE RUPEE NPR"),
("532", "NETHERLANDS ANTILLIA ANG"),
("533", "ARUBA AWG"),
("536", "YUGOSLAVIAN NEW DIAN NTZ"),
("548", "VANUATU VATU VUV"),
("554", "NEW ZEALAND DOLLAR NZD"),
("556", "NAIRA 566"),
("558", "CORDOBA NIC"),
("566", "NAIRA NGN"),
("578", "NORWEGIAN KRONE NOK"),
("582", "PACIFIC ISLAND PCI"),
("586", "PAKISTAN RUPEE PKR"),
("590", "BALBOA PAB"),
("598", "KINA PGK"),
("600", "GUARANI PYG"),
("604", "PERU INTI PEI"),
("608", "PHILIPPINE PESO PHP"),
("616", "ZLOTY PLZ"),
("624", "GUINEA"),
("626", "TIMOR ESCUDO TPE"),
("634", "QATARI RIAL QAR"),
("642", "LEU ROL"),
("643", "RUSSIAN ROUBLE RUB"),
("646", "RWANDA FRANC RWF"),
("654", "ST.HELENA POUND SHP"),
("678", "DOBRA STD"),
("682", "SAUDI RIYAL SAR"),
("690", "SEYCHELLES RUPEE SCR"),
("694", "LEONE SLL"),
("702", "SINGAPORE DOLLAR SGD"),
("703", "SLOVAK KORUNA SKK"),
("704", "DONG VND"),
("705", "SLOVENIAN TOLAR SIT"),
("706", "SOMALI SHILLING SOS"),
("710", "RAND ZAR"),
("716", "ZIMBABWE DOLLAR ZWD"),
("720", "YEMENI DINAR YDD"),
("728", "SOUTH SUDANESE POUND SSP"),
("736", "SUDANESE POUND SDP"),
("737", "SUDAN AIRLINES SDA"),
("740", "SURINAM GUILDER SRG"),
("748", "LILANGENI SZL"),
("752", "SWEDISH KRONA SEK"),
("756", "SWISS FRANC CHF"),
("760", "SYRIAN POUND SYP"),
("762", "TAJIK RUBLE TJR"),
("764", "BAHT THB"),
("776", "PA'ANGA TOP"),
("780", "TRINIDAD Y TOBAGO DO TTD"),
("784", "UAE DIRHAM AED"),
("788", "TUNISIAN DINAR TND"),
("792", "TURKISH LIRA TRL"),
("793", "PSEUDO TURKISH LIRA PTL"),
("795", "MANAT TMM"),
("800", "UGANDA SHILLING UGS"),
("804", "KARBOVANET UAK"),
("807", "MACEDONIAN DENAR MKD"),
("810", "RUSSIAN ROUBLE RUR"),
("818", "EGYPTIAN POUND EGP"),
("826", "POUND STERLING GBP"),
("834", "TANZANIAN SHILLING TZS"),
("840", "DOLAR U.S.A. USD"),
("858", "URUGUAYAN PESO UYP"),
("860", "UZBEKISTAN SUM UZS"),
("862", "BOLIVAR VEB"),
("882", "TALA WST"),
("886", "YEMINI RIAL YER"),
("890", "NEW YUGOSLAVIAN DOLL YUD"),
("891", "NEW DINAR YUG"),
("894", "KWACHA ZMK"),
("901", "NEW TAIWAN DOLLAR TWD"),
("934", "NEW MANAT TMT"),
("936", "GHANA CEDI GHS"),
("941", "DINAR SERBIO RSD"),
("943", "MOZAMBIQUE METICAL MZN"),
("944", "AZERBAIJANIAN MANAT AZN"),
("946", "NEW LEU RON"),
("949", "TURKISH LIRA TRY"),
("950", "CFA FRANC BEAC XAF"),
("951", "EAST CARIBBEAN DOLLA XCD"),
("952", "CFA FRANC BCEAO XOF"),
("953", "CFP FRANC XPF"),
("954", "E.C.U. EUROPEAN CUR XEU"),
("967", "KWACHA ZMW"),
("968", "SURINAME DOLLAR SRD"),
("969", "ARIARY MGA"),
("971", "AFGHANISTAN AFGHANI AFN"),
("972", "TAJIKISTAN SOMONI TJS"),
("973", "KWANZA ANGOLA AOA"),
("974", "BELARUSSIAN RUBLE BYR"),
("975", "NEW LEV BGN"),
("976", "FRANCO DEL CONGO CDF"),
("977", "BOSNIAN MARKA BAM"),
("978", "EURO EUR"),
("980", "HRYVNIA UAH"),
("981", "GEORGIAN LARI GEL"),
("985", "NEW POLISH ZLOTY PLN"),
("986", "BRAZILIAN REAL BRL"),
("991", "RAND FINANCIER ZAL"),
],
default="978",
max_length=4,
verbose_name="Código de moneda",
),
),
("terminal", models.CharField(max_length=8, verbose_name="Terminal")),
(
"shared_secret",
models.CharField(max_length=100, verbose_name="Clave de Redsys"),
),
(
"tpv_domain",
models.URLField(
blank=True,
max_length=128,
null=True,
verbose_name="dominio del tpv",
),
),
],
options={
"verbose_name": "ajustes de la tienda",
"verbose_name_plural": "ajustes de la tienda",
},
),
migrations.CreateModel(
name="Tag",
fields=[
@@ -332,12 +583,26 @@ class Migration(migrations.Migration):
db_index=True, default=uuid.uuid4, verbose_name="UUID"
),
),
(
"code",
models.CharField(
default=shop.models.create_order_code,
max_length=20,
unique=True,
),
),
(
"status",
models.CharField(
default="PEN", max_length=3, verbose_name="estado"
),
),
(
"shipping_status",
models.CharField(
default="NOT", max_length=3, verbose_name="estado de envío"
),
),
(
"base_total",
models.DecimalField(
@@ -416,6 +681,25 @@ class Migration(migrations.Migration):
blank=True, max_length=32, verbose_name="teléfono de contacto"
),
),
(
"amount_paid",
models.DecimalField(
decimal_places=2,
default=Decimal("0.00"),
max_digits=11,
verbose_name="cantidad pagada",
),
),
(
"from_cart",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shop.cart",
verbose_name="carrito de origen de pedido",
),
),
(
"user",
models.ForeignKey(
@@ -430,6 +714,69 @@ class Migration(migrations.Migration):
options={
"verbose_name": "pedido",
"verbose_name_plural": "pedidos",
"ordering": ("creation_date",),
},
),
migrations.CreateModel(
name="Payment",
fields=[
(
"hash",
models.UUIDField(
default=uuid.uuid4,
primary_key=True,
serialize=False,
verbose_name="Hash",
),
),
(
"amount",
models.DecimalField(
decimal_places=2,
default=Decimal("0"),
max_digits=8,
verbose_name="Cantidad",
),
),
(
"creation_date",
models.DateTimeField(
auto_now_add=True, verbose_name="Fecha de creación"
),
),
("metadata", models.JSONField(default=dict, verbose_name="Metadata")),
(
"method",
models.CharField(
choices=[("REDSYS", "Redsys")],
default="REDSYS",
max_length=6,
verbose_name="método de pago",
),
),
(
"order",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="payments",
to="shop.order",
verbose_name="pedido",
),
),
(
"user",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
verbose_name="cliente",
),
),
],
options={
"verbose_name": "Pago",
"verbose_name_plural": "Pagos",
},
),
migrations.CreateModel(
@@ -501,6 +848,12 @@ class Migration(migrations.Migration):
db_index=True, default=False, verbose_name="oculto"
),
),
(
"is_shipping_method",
models.BooleanField(
default=False, verbose_name="es forma de envío"
),
),
(
"brand",
models.ForeignKey(
@@ -511,12 +864,6 @@ class Migration(migrations.Migration):
verbose_name="marca",
),
),
(
"images",
models.ManyToManyField(
blank=True, to="files.fileupload", verbose_name="imágenes"
),
),
],
options={
"verbose_name": "producto",
@@ -708,6 +1055,110 @@ class Migration(migrations.Migration):
blank=True, to="shop.productcategory", verbose_name="categorías"
),
),
migrations.CreateModel(
name="ProductImage",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"original",
models.ImageField(
upload_to="uploads", verbose_name="Imagen original"
),
),
(
"xl",
models.ImageField(
blank=True, null=True, upload_to="uploads", verbose_name="XL"
),
),
(
"xl_dark",
models.ImageField(
blank=True,
null=True,
upload_to="uploads",
verbose_name="XL (fondo oscuro)",
),
),
(
"l",
models.ImageField(
blank=True, null=True, upload_to="uploads", verbose_name="L"
),
),
(
"l_dark",
models.ImageField(
blank=True,
null=True,
upload_to="uploads",
verbose_name="L (fondo oscuro)",
),
),
(
"m",
models.ImageField(
blank=True, null=True, upload_to="uploads", verbose_name="M"
),
),
(
"m_dark",
models.ImageField(
blank=True,
null=True,
upload_to="uploads",
verbose_name="M (fondo oscuro)",
),
),
(
"s",
models.ImageField(
blank=True, null=True, upload_to="uploads", verbose_name="S"
),
),
(
"s_dark",
models.ImageField(
blank=True,
null=True,
upload_to="uploads",
verbose_name="S (fondo oscuro)",
),
),
(
"xs",
models.ImageField(
blank=True, null=True, upload_to="uploads", verbose_name="XS"
),
),
(
"xs_dark",
models.ImageField(
blank=True,
null=True,
upload_to="uploads",
verbose_name="XS (fondo oscuro)",
),
),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="images",
to="shop.product",
verbose_name="Producto",
),
),
],
),
migrations.CreateModel(
name="ProductBatch",
fields=[
@@ -820,6 +1271,17 @@ class Migration(migrations.Migration):
"verbose_name_plural": "métodos de envío",
},
),
migrations.AddField(
model_name="order",
name="shipping_method",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shop.shippingmethod",
verbose_name="método de envío",
),
),
migrations.AddField(
model_name="product",
name="tags",
@@ -1,344 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-12 13:03
import uuid
from decimal import Decimal
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import shop.models
class Migration(migrations.Migration):
dependencies = [
("shop", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="ShopSettings",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"merchant_code",
models.CharField(
max_length=9, verbose_name="Identificación de comercio"
),
),
(
"currency_code",
models.CharField(
choices=[
("8", "LEK ALL"),
("12", "ALGERIAN DINAR DZD"),
("24", "ANGOLA KWANZA AOK"),
("30", "PROBANDO DESA MON"),
("31", "AZERBAIJANIAN MANAT AZM"),
("32", "ARGENTINE AUSTRAL ARP"),
("36", "AUSTRALIAN DOLLAR AUD"),
("44", "BAHAMIAN DOLLAR BSD"),
("48", "BAHRAINI DINAR BHD"),
("50", "TAKA BDT"),
("51", "ARMENIAN DRAM AMD"),
("52", "BARBADOS DOLLAR BBD"),
("60", "BERMUDAN DOLLAR BMD"),
("64", "NGULTRUM BTN"),
("68", "BOLIVIAN PESO BOP"),
("70", "DINAR BAD"),
("72", "PULA BWP"),
("76", "CRUZEIRO BRC"),
("84", "BELIZE DOLLAR 084"),
("90", "SOLOMON ISLANDS DOLL SBD"),
("96", "BRUNEI DOLLAR BND"),
("100", "LEV BGL"),
("104", "KYAT BUK"),
("108", "BURUNDI FRANC BIF"),
("112", "BELARUSSIAN RUBLE BYB"),
("116", "RIEL KHR"),
("124", "CANADIAN DOLLAR CAD"),
("132", "CAPE VERDE ESCUDO CVE"),
("136", "CAYMAN ISLANDS DOLLA KYD"),
("144", "SRI LANKA RUPEE LKR"),
("152", "CHILEAN PESO CLP"),
("156", "YUAN RENMINBI CNY"),
("157", "CHINESE RENMIMBI CNH"),
("158", "CHINESE RENMINBI CNX"),
("170", "COLOMBIAN PESO COP"),
("174", "COMOROS FRANC KMF"),
("180", "ZAIRE ZRZ"),
("188", "COSTA RICA COLON CRC"),
("191", "CROATIAN KUNA HRK"),
("192", "CUBAN PESO CUP"),
("196", "CYPRUS POUND CYP"),
("200", "KORUNA CSK"),
("203", "CZECH KORUNA CZK"),
("208", "DANISH KRONE DKK"),
("214", "DOMINICAN PESO DOP"),
("218", "SUCRE ECS"),
("222", "EL SALVADOR COLON SVC"),
("226", "EKWELE GQE"),
("230", "ETHIOPIAN BIRR ETB"),
("232", "ERITREAN NAKTAN ERN"),
("233", "ESTONIAN KROON EEK"),
("238", "FALKLAND ISLANDS FKP"),
("242", "FIJI DOLLAR FJD"),
("262", "DJIBOUTI FRANC DJF"),
("268", "GEORGIAN LARI GEL"),
("270", "DALASI GMD"),
("278", "MARK DER DDR DDM"),
("288", "GHANA CEDI GHC"),
("292", "GIBRALTAR POUND GIP"),
("320", "QUETZAL GTQ"),
("324", "SYLI GNS"),
("328", "GUYANA DOLLAR GYD"),
("332", "GOURDE HTG"),
("340", "LEMPIRA HNL"),
("344", "HONG KONG DOLLAR HKD"),
("348", "FORINT HUF"),
("352", "ICELAND KRONA ISK"),
("356", "INDIAN RUPEE INR"),
("360", "RUPIAH IDR"),
("364", "IRANIAL RIAL IRR"),
("365", "IRANIAN AIRLINE RATE IRA"),
("368", "IRAQI DINAR IQD"),
("376", "ISRAEL SHEKEL ILS"),
("388", "JAMAICAN DOLLAR JMD"),
("392", "YEN JPY"),
("398", "TENGE KZT"),
("400", "JORDANIAN DINAR JOD"),
("404", "KENYAN SHILLING KES"),
("408", "NORTH KOREAN WON KPW"),
("410", "KOREAN WON KRW"),
("414", "KUWAITI DINAR KWD"),
("417", "KYRGYZSTAN SON KGS"),
("418", "KIP LAK"),
("422", "LEBANESE POUND LBP"),
("426", "LESOTHO LOTI LSM"),
("428", "LATVIAN LAT LVL"),
("430", "LIBERIAN DOLLAR LRD"),
("434", "LIBYAN DINAR LYD"),
("440", "LITHUANIAN LITAS LTL"),
("446", "PATACA MOP"),
("450", "MALAGASY FRANC MGF"),
("454", "MALAWI KWACHA MWK"),
("458", "MALASYAN RINGGIT MYR"),
("462", "MALDIVE RUPEE MVR"),
("466", "MALI MLF"),
("470", "MALTESE LIRA MTL"),
("478", "OUGUIYA MRO"),
("480", "MAURITIUS RUPEE MUR"),
("484", "MEXICAN PESO MXP"),
("496", "TUGRIK MNT"),
("498", "MOLDOVIAN LEU MDL"),
("504", "MORROCAN DIRHAM MAD"),
("508", "METICAL MZM"),
("512", "RIAL OMANI OMR"),
("516", "NAMIBIAN DOLLAR NAD"),
("524", "NEPALESE RUPEE NPR"),
("532", "NETHERLANDS ANTILLIA ANG"),
("533", "ARUBA AWG"),
("536", "YUGOSLAVIAN NEW DIAN NTZ"),
("548", "VANUATU VATU VUV"),
("554", "NEW ZEALAND DOLLAR NZD"),
("556", "NAIRA 566"),
("558", "CORDOBA NIC"),
("566", "NAIRA NGN"),
("578", "NORWEGIAN KRONE NOK"),
("582", "PACIFIC ISLAND PCI"),
("586", "PAKISTAN RUPEE PKR"),
("590", "BALBOA PAB"),
("598", "KINA PGK"),
("600", "GUARANI PYG"),
("604", "PERU INTI PEI"),
("608", "PHILIPPINE PESO PHP"),
("616", "ZLOTY PLZ"),
("624", "GUINEA"),
("626", "TIMOR ESCUDO TPE"),
("634", "QATARI RIAL QAR"),
("642", "LEU ROL"),
("643", "RUSSIAN ROUBLE RUB"),
("646", "RWANDA FRANC RWF"),
("654", "ST.HELENA POUND SHP"),
("678", "DOBRA STD"),
("682", "SAUDI RIYAL SAR"),
("690", "SEYCHELLES RUPEE SCR"),
("694", "LEONE SLL"),
("702", "SINGAPORE DOLLAR SGD"),
("703", "SLOVAK KORUNA SKK"),
("704", "DONG VND"),
("705", "SLOVENIAN TOLAR SIT"),
("706", "SOMALI SHILLING SOS"),
("710", "RAND ZAR"),
("716", "ZIMBABWE DOLLAR ZWD"),
("720", "YEMENI DINAR YDD"),
("728", "SOUTH SUDANESE POUND SSP"),
("736", "SUDANESE POUND SDP"),
("737", "SUDAN AIRLINES SDA"),
("740", "SURINAM GUILDER SRG"),
("748", "LILANGENI SZL"),
("752", "SWEDISH KRONA SEK"),
("756", "SWISS FRANC CHF"),
("760", "SYRIAN POUND SYP"),
("762", "TAJIK RUBLE TJR"),
("764", "BAHT THB"),
("776", "PA'ANGA TOP"),
("780", "TRINIDAD Y TOBAGO DO TTD"),
("784", "UAE DIRHAM AED"),
("788", "TUNISIAN DINAR TND"),
("792", "TURKISH LIRA TRL"),
("793", "PSEUDO TURKISH LIRA PTL"),
("795", "MANAT TMM"),
("800", "UGANDA SHILLING UGS"),
("804", "KARBOVANET UAK"),
("807", "MACEDONIAN DENAR MKD"),
("810", "RUSSIAN ROUBLE RUR"),
("818", "EGYPTIAN POUND EGP"),
("826", "POUND STERLING GBP"),
("834", "TANZANIAN SHILLING TZS"),
("840", "DOLAR U.S.A. USD"),
("858", "URUGUAYAN PESO UYP"),
("860", "UZBEKISTAN SUM UZS"),
("862", "BOLIVAR VEB"),
("882", "TALA WST"),
("886", "YEMINI RIAL YER"),
("890", "NEW YUGOSLAVIAN DOLL YUD"),
("891", "NEW DINAR YUG"),
("894", "KWACHA ZMK"),
("901", "NEW TAIWAN DOLLAR TWD"),
("934", "NEW MANAT TMT"),
("936", "GHANA CEDI GHS"),
("941", "DINAR SERBIO RSD"),
("943", "MOZAMBIQUE METICAL MZN"),
("944", "AZERBAIJANIAN MANAT AZN"),
("946", "NEW LEU RON"),
("949", "TURKISH LIRA TRY"),
("950", "CFA FRANC BEAC XAF"),
("951", "EAST CARIBBEAN DOLLA XCD"),
("952", "CFA FRANC BCEAO XOF"),
("953", "CFP FRANC XPF"),
("954", "E.C.U. EUROPEAN CUR XEU"),
("967", "KWACHA ZMW"),
("968", "SURINAME DOLLAR SRD"),
("969", "ARIARY MGA"),
("971", "AFGHANISTAN AFGHANI AFN"),
("972", "TAJIKISTAN SOMONI TJS"),
("973", "KWANZA ANGOLA AOA"),
("974", "BELARUSSIAN RUBLE BYR"),
("975", "NEW LEV BGN"),
("976", "FRANCO DEL CONGO CDF"),
("977", "BOSNIAN MARKA BAM"),
("978", "EURO EUR"),
("980", "HRYVNIA UAH"),
("981", "GEORGIAN LARI GEL"),
("985", "NEW POLISH ZLOTY PLN"),
("986", "BRAZILIAN REAL BRL"),
("991", "RAND FINANCIER ZAL"),
],
default="978",
max_length=4,
verbose_name="Código de moneda",
),
),
("terminal", models.CharField(max_length=8, verbose_name="Terminal")),
(
"shared_secret",
models.CharField(max_length=100, verbose_name="Clave de Redsys"),
),
],
options={
"verbose_name": "ajustes de la tienda",
"verbose_name_plural": "ajustes de la tienda",
},
),
migrations.AddField(
model_name="order",
name="code",
field=models.CharField(
default=shop.models.create_order_code, max_length=20, unique=True
),
),
migrations.AddField(
model_name="order",
name="shipping_status",
field=models.CharField(
default="NOT", max_length=3, verbose_name="estado de envío"
),
),
migrations.AddField(
model_name="product",
name="is_shipping_method",
field=models.BooleanField(default=False, verbose_name="es forma de envío"),
),
migrations.CreateModel(
name="Payment",
fields=[
(
"hash",
models.UUIDField(
default=uuid.uuid4,
primary_key=True,
serialize=False,
verbose_name="Hash",
),
),
(
"amount",
models.DecimalField(
decimal_places=2,
default=Decimal("0"),
max_digits=8,
verbose_name="Cantidad",
),
),
(
"creation_date",
models.DateTimeField(
auto_now_add=True, verbose_name="Fecha de creación"
),
),
("metadata", models.JSONField(default=dict, verbose_name="Metadata")),
(
"method",
models.CharField(
choices=[("REDSYS", "Redsys")],
default="REDSYS",
max_length=6,
verbose_name="método de pago",
),
),
(
"order",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="shop.order",
verbose_name="pedido",
),
),
(
"user",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
verbose_name="cliente",
),
),
],
options={
"verbose_name": "Pago",
"verbose_name_plural": "Pagos",
},
),
]
@@ -1,35 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-12 22:15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0002_shopsettings_order_code_order_shipping_status_and_more"),
]
operations = [
migrations.AddField(
model_name="order",
name="shipping_method",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shop.shippingmethod",
verbose_name="método de envío",
),
),
migrations.AlterField(
model_name="payment",
name="order",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="payments",
to="shop.order",
verbose_name="pedido",
),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-13 08:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0003_order_shipping_method_alter_payment_order"),
]
operations = [
migrations.AddField(
model_name="shopsettings",
name="tpv_domain",
field=models.URLField(
blank=True, max_length=128, null=True, verbose_name="dominio del tpv"
),
),
]
@@ -1,45 +0,0 @@
# Generated by Django 5.1.4 on 2025-01-16 17:00
from decimal import Decimal
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0004_shopsettings_tpv_domain"),
]
operations = [
migrations.AlterModelOptions(
name="order",
options={
"ordering": ("creation_date",),
"verbose_name": "pedido",
"verbose_name_plural": "pedidos",
},
),
migrations.AddField(
model_name="order",
name="amount_paid",
field=models.DecimalField(
decimal_places=2,
default=Decimal("0.00"),
max_digits=11,
verbose_name="cantidad pagada",
),
),
migrations.AddField(
model_name="order",
name="from_cart",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shop.cart",
verbose_name="carrito de origen de pedido",
),
),
]
+104 -3
View File
@@ -1,13 +1,17 @@
from decimal import Decimal
from io import BytesIO
from uuid import uuid4
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.files.base import ContentFile
from django.db import models
from django.utils import timezone
from django.utils.text import gettext_lazy as _
from django.utils.text import slugify
from PIL import Image
from config.models import SingletonModel
from shop.settings import CURRENCY_CODES
@@ -103,9 +107,6 @@ class Product(TimestampedModel):
default=False, verbose_name=_("es un activo digital")
)
url = models.URLField(blank=True, verbose_name=_("URL de descarga"))
images = models.ManyToManyField(
"files.FileUpload", blank=True, verbose_name=_("imágenes")
)
tags = models.ManyToManyField("shop.Tag", blank=True, verbose_name=_("etiquetas"))
categories = models.ManyToManyField(
"shop.ProductCategory", blank=True, verbose_name=_("categorías")
@@ -182,6 +183,106 @@ class ProductPrice(TimestampedModel):
ordering = ("-date",)
class ProductImage(models.Model):
product = models.ForeignKey(
"shop.Product",
on_delete=models.CASCADE,
blank=False,
null=False,
verbose_name=_("Producto"),
related_name="images",
)
original = models.ImageField(
upload_to="uploads", blank=False, null=False, verbose_name=_("Imagen original")
)
xl = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("XL")
)
xl_dark = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("XL (fondo oscuro)")
)
l = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("L")
)
l_dark = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("L (fondo oscuro)")
)
m = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("M")
)
m_dark = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("M (fondo oscuro)")
)
s = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("S")
)
s_dark = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("S (fondo oscuro)")
)
xs = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("XS")
)
xs_dark = models.ImageField(
upload_to="uploads", blank=True, null=True, verbose_name=_("XS (fondo oscuro)")
)
def __str__(self):
return self.original.url
def make_square(self, image, max_size=512, fill_color="#000000"):
width, height = image.size
if width > height:
ratio = width / max_size
else:
ratio = height / max_size
new_width = int(width / ratio)
new_height = int(height / ratio)
resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
new_image = Image.new("RGB", (max_size, max_size), fill_color)
position_x = int((max_size - new_width) / 2)
position_y = int((max_size - new_height) / 2)
new_image.paste(resized_image, (position_x, position_y))
return new_image
def get_resized_image(self, image, size, fill_color="#000000"):
image = Image.open(image)
image = self.make_square(image, size, fill_color)
buffer = BytesIO()
image.save(fp=buffer, format="WEBP")
return ContentFile(buffer.getvalue(), name=f"{self.product.slug}_{size}.webp")
def save(self, *args, **kwargs):
self.xl = self.get_resized_image(self.original, 1024, fill_color="#FFFFFF")
self.xl_dark = self.get_resized_image(self.original, 1024)
self.l = self.get_resized_image(self.original, 512, fill_color="#FFFFFF")
self.l_dark = self.get_resized_image(self.original, 512)
self.m = self.get_resized_image(self.original, 256, fill_color="#FFFFFF")
self.m_dark = self.get_resized_image(self.original, 256)
self.s = self.get_resized_image(self.original, 128, fill_color="#FFFFFF")
self.s_dark = self.get_resized_image(self.original, 128)
self.xs = self.get_resized_image(self.original, 96, fill_color="#FFFFFF")
self.xs_dark = self.get_resized_image(self.original, 96)
return super().save(*args, **kwargs)
class ProductBatch(TimestampedModel):
code = models.CharField(
max_length=32, unique=True, db_index=True, verbose_name=_("código")
+50
View File
@@ -0,0 +1,50 @@
from io import BytesIO
from PIL import Image
from django.core.files.base import ContentFile
from django.test import TestCase
from shop.models import ProductImage
from shop.tests.mixins import CreateProductsMixin
class TestProductImages(TestCase, CreateProductsMixin):
def setUp(self):
self.product = self.create_product()
def create_image_file(self, size=(768, 768)):
image = Image.new("RGB", size, "#ACACAC")
buffer = BytesIO()
image.save(fp=buffer, format="WEBP")
file = ContentFile(buffer.getvalue(), name=f"{self.product.slug}.webp")
return file
def test_product_image_creation(self):
file = self.create_image_file()
image = ProductImage.objects.create(product=self.product, original=file)
xl = Image.open(image.xl)
xl_dark = Image.open(image.xl_dark)
assert (1024, 1024) == xl.size == xl_dark.size
l = Image.open(image.l)
l_dark = Image.open(image.l_dark)
assert (512, 512) == l.size == l_dark.size
m = Image.open(image.m)
m_dark = Image.open(image.m_dark)
assert (256, 256) == m.size == m_dark.size
s = Image.open(image.s)
s_dark = Image.open(image.s_dark)
assert (128, 128) == s.size == s_dark.size
xs = Image.open(image.xs)
xs_dark = Image.open(image.xs_dark)
assert (96, 96) == xs.size == xs_dark.size
+1 -5
View File
@@ -30,9 +30,5 @@ class LoginForm(StylingMixin, AuthenticationForm):
remember_me = forms.BooleanField(
label=_("Mantener sesión iniciada"),
required=False,
widget=forms.CheckboxInput(
attrs={
"class": "input-checkbox"
}
),
widget=forms.CheckboxInput(attrs={"class": "input-checkbox"}),
)
+2 -2
View File
@@ -15,9 +15,9 @@
<div class="space-y-4 flex items-center justify-between gap-6 space-y-0">
<a href="{% url 'web:product_detail' pk=item.product.pk slug=item.product.slug %}"
class="shrink-0 md:order-1">
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all|first }}"
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all.0.s.url }}"
alt="{{ item.product.name }}"/>
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all|first }}"
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all.0.s_dark.url }}"
alt="{{ item.product.name }}"/>
</a>
+2 -2
View File
@@ -4,9 +4,9 @@
class="card">
<div class="h-56 w-full">
<a href="{% url 'web:product_detail' pk=product.pk slug=product.slug %}">
<img class="mx-auto h-full dark:hidden" src="{{ product.images.all|first }}"
<img class="mx-auto h-full dark:hidden" src="{{ product.images.all.0.m.url }}"
alt="{{ product.name }}"/>
<img class="mx-auto hidden h-full dark:block" src="{{ product.images.all|first }}"
<img class="mx-auto hidden h-full dark:block" src="{{ product.images.all.0.m_dark.url }}"
alt="{{ product.name }}"/>
</a>
</div>
+2 -2
View File
@@ -23,9 +23,9 @@
class="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800 md:p-6">
<div class="space-y-4 md:flex md:items-center md:justify-between md:gap-6 md:space-y-0">
<a href="#" class="shrink-0 md:order-1">
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all|first }}"
<img class="h-20 w-20 dark:hidden" src="{{ item.product.images.all.0.s.url }}"
alt="{{ item.product.name }}"/>
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all|first }}"
<img class="hidden h-20 w-20 dark:block" src="{{ item.product.images.all.0.s_dark.url }}"
alt="{{ item.product.name }}"/>
</a>
+1 -1
View File
@@ -16,7 +16,7 @@
<sl-carousel-item>
<img
alt="{{ product.name }}"
src="{{ image.file.url }}"
src="{{ image.xl.url }}"
/>
</sl-carousel-item>
{% endfor %}