diff --git a/tienda_ilusion/don_confiao/admin.py b/tienda_ilusion/don_confiao/admin.py index 659a321..0756544 100644 --- a/tienda_ilusion/don_confiao/admin.py +++ b/tienda_ilusion/don_confiao/admin.py @@ -1,5 +1,7 @@ from django.contrib import admin -from .models import Sale, SaleLine +from .models import Sale, SaleLine, Product, ProductCategory admin.site.register(Sale) admin.site.register(SaleLine) +admin.site.register(Product) +admin.site.register(ProductCategory) diff --git a/tienda_ilusion/don_confiao/example_products.csv b/tienda_ilusion/don_confiao/example_products.csv new file mode 100644 index 0000000..46228a7 --- /dev/null +++ b/tienda_ilusion/don_confiao/example_products.csv @@ -0,0 +1,4 @@ +"producto","unidad","precio","categorias" +"Aceite","Unidad", 50000,"Aceites&Alimentos" +"Café","Unidad", 14000,"Cafes&Alimentos" +"Arroz","Unidad", 7000,"Cafes&Alimentos" diff --git a/tienda_ilusion/don_confiao/example_products2.csv b/tienda_ilusion/don_confiao/example_products2.csv new file mode 100644 index 0000000..787e48d --- /dev/null +++ b/tienda_ilusion/don_confiao/example_products2.csv @@ -0,0 +1,4 @@ +"producto","unidad","precio","categorias" +"Aceite","Unidad", 50000,"Aceites&Alimentos" +"Café","Unidad", 15000,"Cafes&Alimentos" +"Arroz","Unidad", 6000,"Alimentos&Granos" diff --git a/tienda_ilusion/don_confiao/forms.py b/tienda_ilusion/don_confiao/forms.py new file mode 100644 index 0000000..a9ef913 --- /dev/null +++ b/tienda_ilusion/don_confiao/forms.py @@ -0,0 +1,4 @@ +from django import forms + +class ImportProductsForm(forms.Form): + csv_file = forms.FileField() diff --git a/tienda_ilusion/don_confiao/migrations/0009_productcategory_product.py b/tienda_ilusion/don_confiao/migrations/0009_productcategory_product.py new file mode 100644 index 0000000..6c7e15f --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0009_productcategory_product.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.6 on 2024-06-29 18:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0008_alter_sale_phone_alter_saleline_description'), + ] + + operations = [ + migrations.CreateModel( + name='ProductCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('price', models.DecimalField(decimal_places=2, max_digits=9)), + ('measuring_unit', models.CharField(choices=[('UNIT', 'Unit')], default='UNIT', max_length=20)), + ('categories', models.ManyToManyField(to='don_confiao.productcategory')), + ], + ), + ] diff --git a/tienda_ilusion/don_confiao/migrations/0010_alter_productcategory_name.py b/tienda_ilusion/don_confiao/migrations/0010_alter_productcategory_name.py new file mode 100644 index 0000000..c400651 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0010_alter_productcategory_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-29 21:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0009_productcategory_product'), + ] + + operations = [ + migrations.AlterField( + model_name='productcategory', + name='name', + field=models.CharField(max_length=100, unique=True), + ), + ] diff --git a/tienda_ilusion/don_confiao/migrations/0011_alter_product_name.py b/tienda_ilusion/don_confiao/migrations/0011_alter_product_name.py new file mode 100644 index 0000000..9a6a092 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0011_alter_product_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-29 21:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0010_alter_productcategory_name'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='name', + field=models.CharField(max_length=100, unique=True), + ), + ] diff --git a/tienda_ilusion/don_confiao/models.py b/tienda_ilusion/don_confiao/models.py index afe69e5..6d40cac 100644 --- a/tienda_ilusion/don_confiao/models.py +++ b/tienda_ilusion/don_confiao/models.py @@ -1,5 +1,5 @@ from django.db import models - +from django.utils.translation import gettext_lazy as _ class Sale(models.Model): @@ -16,3 +16,25 @@ class SaleLine(models.Model): quantity = models.IntegerField(null=True) unit_price = models.DecimalField(max_digits=9, decimal_places=2) description = models.CharField(max_length=255, null=True, blank=True) + +class MeasuringUnits(models.TextChoices): + UNIT = 'UNIT', _('Unit') + +class ProductCategory(models.Model): + name = models.CharField(max_length=100, unique=True) + + def __str__(self): + return self.name + +class Product(models.Model): + name = models.CharField(max_length=100, unique=True) + price = models.DecimalField(max_digits=9, decimal_places=2) + measuring_unit = models.CharField( + max_length=20, + choices=MeasuringUnits.choices, + default=MeasuringUnits.UNIT + ) + categories = models.ManyToManyField(ProductCategory) + + def __str__(self): + return self.name diff --git a/tienda_ilusion/don_confiao/templates/don_confiao/import_products.html b/tienda_ilusion/don_confiao/templates/don_confiao/import_products.html new file mode 100644 index 0000000..376952d --- /dev/null +++ b/tienda_ilusion/don_confiao/templates/don_confiao/import_products.html @@ -0,0 +1,10 @@ +{% if form.is_multipart %} +
+{% else %} + +{% endif %} + +{% csrf_token %} +{{ form }} + +
diff --git a/tienda_ilusion/don_confiao/templates/don_confiao/index.html b/tienda_ilusion/don_confiao/templates/don_confiao/index.html index 2fbf6b4..7b791c0 100644 --- a/tienda_ilusion/don_confiao/templates/don_confiao/index.html +++ b/tienda_ilusion/don_confiao/templates/don_confiao/index.html @@ -2,5 +2,6 @@

Don Confiao

diff --git a/tienda_ilusion/don_confiao/test_products.py b/tienda_ilusion/don_confiao/test_products.py new file mode 100644 index 0000000..0b4e0dc --- /dev/null +++ b/tienda_ilusion/don_confiao/test_products.py @@ -0,0 +1,106 @@ +from django.test import Client, TestCase +from django.contrib.auth.models import AnonymousUser, User +from django.conf import settings + +from .views import import_products, products +from .models import ProductCategory, Product + +import os +import json + +class TestProducts(TestCase): + def setUp(self): + self.client = Client() + + def test_import_products(self): + self._import_csv() + all_products = self._get_products() + self.assertEqual( + len(all_products), + 3 + ) + + def test_import_products_with_categories(self): + self._import_csv() + all_products = self._get_products() + self.assertIn("Aceites", all_products[0]["categories"]) + + def test_don_repeat_categories_on_import(self): + self._import_csv() + categories_on_csv = ["Cafes", "Alimentos", "Aceites"] + categories = ProductCategory.objects.all() + self.assertCountEqual( + [c.name for c in categories], + categories_on_csv + ) + + def test_update_products(self): + self._import_csv() + first_products = self._get_products() + self._import_csv('example_products2.csv') + seconds_products = self._get_products() + self.assertEqual(len(first_products), len(seconds_products)) + + def test_preserve_id_on_import(self): + self._import_csv() + id_aceite = Product.objects.get(name='Aceite').id + self._import_csv('example_products2.csv') + id_post_updated = Product.objects.get(name='Aceite').id + self.assertEqual(id_aceite, id_post_updated) + + def test_update_categories_on_import(self): + self._import_csv() + first_products = self._get_products() + first_categories = {p["name"]: p["categories"] for p in first_products} + self._import_csv('example_products2.csv') + updated_products = self._get_products() + updated_categories = {p["name"]: p["categories"] for p in updated_products} + + self.assertIn('Cafes', first_categories['Arroz']) + self.assertNotIn('Granos', first_categories['Arroz']) + + self.assertIn('Granos', updated_categories['Arroz']) + self.assertNotIn('Cafes', updated_categories['Arroz']) + + + def test_update_price(self): + self._import_csv() + first_products = self._get_products() + first_prices = {p["name"]: p["price_list"] for p in first_products} + expected_first_prices = { + "Aceite": '50000.00', + "Café": '14000.00', + "Arroz": '7000.00' + } + self.assertDictEqual( + expected_first_prices, + first_prices + ) + + self._import_csv('example_products2.csv') + updated_products = self._get_products() + updated_prices = {p["name"]: p["price_list"] for p in updated_products} + expected_updated_prices = { + "Aceite": '50000.00', + "Café": '15000.00', + "Arroz": '6000.00' + } + + self.assertDictEqual( + expected_updated_prices, + updated_prices + ) + + def _get_products(self): + products_response = self.client.get("/don_confiao/productos") + return json.loads(products_response.content.decode('utf-8')) + + def _import_csv(self, csv_file='example_products.csv'): + app_name = "don_confiao" + app_dir = os.path.join(settings.BASE_DIR, app_name) + example_csv = os.path.join(app_dir, csv_file) + with open(example_csv, 'rb') as csv: + self.client.post( + "/don_confiao/importar_productos", + {"csv_file": csv} + ) diff --git a/tienda_ilusion/don_confiao/urls.py b/tienda_ilusion/don_confiao/urls.py index bb2e394..a9b716f 100644 --- a/tienda_ilusion/don_confiao/urls.py +++ b/tienda_ilusion/don_confiao/urls.py @@ -2,9 +2,11 @@ from django.urls import path from . import views +app_name = 'don_confiao' urlpatterns = [ path("", views.index, name="wellcome"), path("comprar", views.buy, name="buy"), path("compras", views.purchases, name="purchases"), path("productos", views.products, name="products"), + path("importar_productos", views.import_products, name="import_products") ] diff --git a/tienda_ilusion/don_confiao/views.py b/tienda_ilusion/don_confiao/views.py index 8a8af33..c4b7a5b 100644 --- a/tienda_ilusion/don_confiao/views.py +++ b/tienda_ilusion/don_confiao/views.py @@ -1,9 +1,12 @@ from django.shortcuts import render -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.template import loader -from .models import Sale +from .models import Sale, Product, ProductCategory +from .forms import ImportProductsForm +import csv +import io def index(request): return render(request, 'don_confiao/index.html') @@ -22,18 +25,53 @@ def purchases(request): def products(request): - products = [ - { - "name": "Aceite de Coco Artesanal 500ml", - "price_list": 50000, - "uom": "Unit", - "category": "Aceites" - }, - { - "name": "Cafe 500ml", - "price_list": 14000, - "uom": "Unit", - "category": "Cafes" - }, - ] - return JsonResponse(products, safe=False) \ No newline at end of file + rproducts = [] + products = Product.objects.all() + for product in products: + rproduct = { + "name": product.name, + "price_list": product.price, + "uom": product.measuring_unit, + "categories": [c.name for c in product.categories.all()] + } + rproducts.append(rproduct) + + return JsonResponse(rproducts, safe=False) + +def import_products(request): + if request.method == "POST": + form = ImportProductsForm(request.POST, request.FILES) + if form.is_valid(): + handle_import_products_file(request.FILES["csv_file"]) + return HttpResponseRedirect("productos") + else: + form = ImportProductsForm() + return render( + request, + "don_confiao/import_products.html", + {'form': form} + ) + +def _categories_from_csv_string(categories_string, separator="&"): + categories = categories_string.split(separator) + clean_categories = [c.strip() for c in categories] + return [_category_from_name(category) for category in clean_categories] + +def _category_from_name(name): + return ProductCategory.objects.get_or_create(name=name)[0] + +def handle_import_products_file(csv_file): + data = io.StringIO(csv_file.read().decode('utf-8')) + reader = csv.DictReader(data, quotechar='"') + for row in reader: + product, created = Product.objects.update_or_create( + name=row['producto'], + defaults={ + 'price': row['precio'], + 'measuring_unit': row['unidad'] + } + ) + categories = _categories_from_csv_string(row["categorias"]) + product.categories.clear() + for category in categories: + product.categories.add(category)