Merge branch 'main' of ssh://gitea.onecluster.org:6666/OneTeam/don_confiao
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
							
								
								
									
										4
									
								
								tienda_ilusion/don_confiao/example_products.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tienda_ilusion/don_confiao/example_products.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| "producto","unidad","precio","categorias" | ||||
| "Aceite","Unidad", 50000,"Aceites&Alimentos" | ||||
| "Café","Unidad", 14000,"Cafes&Alimentos" | ||||
| "Arroz","Unidad", 7000,"Cafes&Alimentos" | ||||
| 
 | 
							
								
								
									
										4
									
								
								tienda_ilusion/don_confiao/example_products2.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tienda_ilusion/don_confiao/example_products2.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| "producto","unidad","precio","categorias" | ||||
| "Aceite","Unidad", 50000,"Aceites&Alimentos" | ||||
| "Café","Unidad", 15000,"Cafes&Alimentos" | ||||
| "Arroz","Unidad", 6000,"Alimentos&Granos" | ||||
| 
 | 
							
								
								
									
										4
									
								
								tienda_ilusion/don_confiao/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tienda_ilusion/don_confiao/forms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| from django import forms | ||||
|  | ||||
| class ImportProductsForm(forms.Form): | ||||
|     csv_file = forms.FileField() | ||||
| @@ -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')), | ||||
|             ], | ||||
|         ), | ||||
|     ] | ||||
| @@ -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), | ||||
|         ), | ||||
|     ] | ||||
| @@ -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), | ||||
|         ), | ||||
|     ] | ||||
| @@ -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 | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| {% if form.is_multipart %} | ||||
|     <form enctype="multipart/form-data" method="post"> | ||||
| {% else %} | ||||
|     <form method="post"> | ||||
| {% endif %} | ||||
|  | ||||
| {% csrf_token %} | ||||
| {{ form }} | ||||
| <input type="submit" value="Importar"> | ||||
| </form> | ||||
| @@ -2,5 +2,6 @@ | ||||
| <h2>Don Confiao</h2> | ||||
| <ul> | ||||
|     <li><a href='./comprar'>Comprar</a></li> | ||||
|     <li><a href='./compras'>Compras</a></li> | ||||
|     <li><a href='./productos'>Productos</a></li> | ||||
|     <li><a href='./importar_productos'>Importar Productos</a></li> | ||||
| </ul> | ||||
|   | ||||
							
								
								
									
										106
									
								
								tienda_ilusion/don_confiao/test_products.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								tienda_ilusion/don_confiao/test_products.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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} | ||||
|             ) | ||||
| @@ -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") | ||||
| ] | ||||
|   | ||||
| @@ -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') | ||||
| @@ -28,19 +31,54 @@ 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) | ||||
|     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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user