diff --git a/webportal/management/commands/delete_assembly_cells.py b/webportal/management/commands/delete_assembly_cells.py new file mode 100644 index 0000000..ee2b042 --- /dev/null +++ b/webportal/management/commands/delete_assembly_cells.py @@ -0,0 +1,17 @@ +from django.core.management.base import BaseCommand + +from webportal.models import Cell + + +class Command(BaseCommand): + help = """ + Delete all the Assembly cells from the database. + To run: 'python manage.py delete_assembly_cells' + """ + + def handle(self, *args, **kwargs): + num_assemblies = Cell.objects.count() + Cell.objects.all().delete() + self.stdout.write( + self.style.SUCCESS(f"{num_assemblies} assembly-cells deleted successfully.") + ) diff --git a/webportal/management/commands/delete_assembly_layers.py b/webportal/management/commands/delete_assembly_layers.py new file mode 100644 index 0000000..f8432a3 --- /dev/null +++ b/webportal/management/commands/delete_assembly_layers.py @@ -0,0 +1,19 @@ +from django.core.management.base import BaseCommand + +from webportal.models import Layer + + +class Command(BaseCommand): + help = """ + Delete all the Assembly cells from the database. + To run: 'python manage.py delete_assembly_layers' + """ + + def handle(self, *args, **kwargs): + num_assemblies = Layer.objects.count() + Layer.objects.all().delete() + self.stdout.write( + self.style.SUCCESS( + f"{num_assemblies} assembly-layers deleted successfully." + ) + ) diff --git a/webportal/migrations/0010_remove_cell_column_number_remove_cell_container_and_more.py b/webportal/migrations/0010_remove_cell_column_number_remove_cell_container_and_more.py new file mode 100644 index 0000000..83d800f --- /dev/null +++ b/webportal/migrations/0010_remove_cell_column_number_remove_cell_container_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 5.1.3 on 2024-12-12 17:25 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("webportal", "0009_alter_material_unique_id"), + ] + + operations = [ + migrations.RemoveField( + model_name="cell", + name="column_number", + ), + migrations.RemoveField( + model_name="cell", + name="container", + ), + migrations.RemoveField( + model_name="cell", + name="row_number", + ), + migrations.AddField( + model_name="assembly", + name="layer_id_order", + field=models.JSONField(default=list), + ), + migrations.AlterField( + model_name="material", + name="unique_id", + field=models.CharField(default="90dd06", max_length=6, unique=True), + ), + migrations.CreateModel( + name="Layer", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("thickness", models.FloatField()), + ( + "assembly", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="layers", + to="webportal.assembly", + ), + ), + ], + ), + migrations.AddField( + model_name="cell", + name="layer", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="cells", + to="webportal.layer", + ), + ), + ] diff --git a/webportal/migrations/0011_alter_material_unique_id.py b/webportal/migrations/0011_alter_material_unique_id.py new file mode 100644 index 0000000..e45cc09 --- /dev/null +++ b/webportal/migrations/0011_alter_material_unique_id.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.3 on 2024-12-12 17:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("webportal", "0010_remove_cell_column_number_remove_cell_container_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="material", + name="unique_id", + field=models.CharField(default="2d95fc", max_length=6, unique=True), + ), + ] diff --git a/webportal/migrations/0012_alter_material_unique_id.py b/webportal/migrations/0012_alter_material_unique_id.py new file mode 100644 index 0000000..f656846 --- /dev/null +++ b/webportal/migrations/0012_alter_material_unique_id.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.3 on 2024-12-12 18:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("webportal", "0011_alter_material_unique_id"), + ] + + operations = [ + migrations.AlterField( + model_name="material", + name="unique_id", + field=models.CharField(default="7f013d", max_length=6, unique=True), + ), + ] diff --git a/webportal/models.py b/webportal/models.py index 83bc735..c12b6b0 100644 --- a/webportal/models.py +++ b/webportal/models.py @@ -3,6 +3,8 @@ from django.contrib.auth.models import AbstractUser from django.db import models +from typing import Any + # --------------------------------------------------------------------------------------- # -- Users @@ -114,21 +116,62 @@ class Assembly(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False) name = models.CharField(max_length=100, default="assembly") created_at = models.DateTimeField(auto_now_add=True) + layer_id_order = models.JSONField(default=list) + + def get_ordered_layers(self) -> list["Layer"]: + """Return layers ordered according to the layer_id_order field.""" + # Sort layers by the order in layer_order + layers = {layer.id: layer for layer in self.layers.all()} + return [ + layers[layer_id] for layer_id in self.layer_id_order if layer_id in layers + ] + + def delete_layer(self, layer_pk: int) -> None: + """Delete a layer from the assembly.""" + if layer_pk in self.layer_id_order: + self.layer_id_order.remove(layer_pk) + self.save() + + def save(self, *args, **kwargs) -> None: + super().save(*args, **kwargs) + layers = Layer.objects.filter(assembly=self) + if not layers.exists(): + new_layer = Layer.objects.create(assembly=self, thickness=1.0) + self.layer_id_order.append(new_layer.id) - def save(self, *args, **kwargs): + @property + def layers(self) -> models.QuerySet["Layer"]: + return self.layer_set.all() # type: ignore + + def __str__(self) -> str: + return self.name + + +class Layer(models.Model): + assembly = models.ForeignKey( + Assembly, on_delete=models.CASCADE, related_name="layers" + ) + thickness = models.FloatField() + + def save(self, *args, **kwargs) -> None: super().save(*args, **kwargs) - cells = Cell.objects.filter(container=self) + cells = Cell.objects.filter(layer=self) if not cells.exists(): - Cell.objects.create(container=self, column_number=1, row_number=1) + Cell.objects.create(layer=self) + + @property + def id(self) -> int: + return self.id + + def __str__(self) -> str: + return f"{self.thickness} mm" class Cell(models.Model): - container = models.ForeignKey( - Assembly, on_delete=models.CASCADE, related_name="cells" + layer = models.ForeignKey( + Layer, on_delete=models.CASCADE, null=True, related_name="cells" ) - column_number = models.IntegerField() - row_number = models.IntegerField() value = models.TextField(blank=True, null=True) def __str__(self): - return f"{self.container.name} Cell: [{self.row_number}:{self.column_number}]" + return f"[layer-{self.layer}]: {self.value}" diff --git a/webportal/templates/webportal/partials/assemblies/assembly-detail.html b/webportal/templates/webportal/partials/assemblies/assembly-detail.html deleted file mode 100644 index 3aebab9..0000000 --- a/webportal/templates/webportal/partials/assemblies/assembly-detail.html +++ /dev/null @@ -1,26 +0,0 @@ -
- -
- {% for row in row_range %} -
- {% for col in col_range %} -
- - {% comment %} - {% for cell in cells %} - {% if cell.x == col and cell.y == row %} - {% endif %} - {% endfor %} - {% endcomment %} -
- {% endfor %} -
- {% endfor %} -
- -
- - -
- -
\ No newline at end of file diff --git a/webportal/templates/webportal/partials/assemblies/assembly.html b/webportal/templates/webportal/partials/assemblies/assembly.html index 3e6d3b3..0661871 100644 --- a/webportal/templates/webportal/partials/assemblies/assembly.html +++ b/webportal/templates/webportal/partials/assemblies/assembly.html @@ -21,7 +21,66 @@ {% endblock %} + + {% block assembly-detail-view %} +
+ +
+ {% for layer in layers %} + + {% block layer %} +
+
+ + + + + +
+ +
+ +
+
+ {% endblock %} - {% include "webportal/partials/assemblies/assembly-detail.html" %} + {% endfor %} +
+ +
+ +
+ +
+ {% endblock %} + {% endif %} diff --git a/webportal/templates/webportal/partials/assemblies/row.html b/webportal/templates/webportal/partials/assemblies/row.html deleted file mode 100644 index 64e305a..0000000 --- a/webportal/templates/webportal/partials/assemblies/row.html +++ /dev/null @@ -1,8 +0,0 @@ -
- {% for cell in cells %} - {% comment %}
{{ cell.value }}
{% endcomment %} -
- -
- {% endfor %} -
\ No newline at end of file diff --git a/webportal/templates/webportal/partials/assemblies/sidebar.html b/webportal/templates/webportal/partials/assemblies/sidebar.html index de7c488..fad4842 100644 --- a/webportal/templates/webportal/partials/assemblies/sidebar.html +++ b/webportal/templates/webportal/partials/assemblies/sidebar.html @@ -3,7 +3,51 @@
- {% include 'webportal/partials/assemblies/sidebar_list.html' with assemblies=assemblies active_assembly_id=active_assembly_id %} + {% block assembly-sidebar-list %} + + + + {% endblock %}
- {% for assembly in assemblies %} -
  • - -
  • - {% endfor %} - \ No newline at end of file diff --git a/webportal/urls.py b/webportal/urls.py index c8ebbb2..e3ed7e8 100644 --- a/webportal/urls.py +++ b/webportal/urls.py @@ -42,11 +42,15 @@ ), # ----------------------------------------------------------------------------------- # -- Assembly Details + path("assemblies//add-layer/", views.add_layer, name="add-layer"), path( - "assemblies//assembly-detail/", - views.assembly_detail, - name="assembly-detail", + "assemblies//delete-layer//", + views.delete_layer, + name="delete-layer", + ), + path( + "assemblies//update-layer-thickness//", + views.update_layer_thickness, + name="update-layer-thickness", ), - path("assemblies//add-row/", views.add_row, name="add-row"), - path("assemblies//add-column/", views.add_column, name="add-column"), ] diff --git a/webportal/views.py b/webportal/views.py index 9ee7b47..c5a9ef7 100644 --- a/webportal/views.py +++ b/webportal/views.py @@ -18,7 +18,7 @@ from webportal.filters import MaterialCategoryFilter from webportal.forms import MaterialForm -from webportal.models import Material, Cell, Assembly +from webportal.models import Material, Assembly, Layer, Cell from webportal.resources import MaterialResource @@ -236,43 +236,40 @@ def assemblies_page(request: WSGIRequest) -> HttpResponse: @login_required -def assembly(request: WSGIRequest, pk: int) -> HttpResponse: +def assembly(request: WSGIRequest, pk: int | None) -> HttpResponse: """The Assembly view with the sidebar. - Sidebar includes the 'active' assembly. + Sidebar includes the 'active' assembly highlighted. """ + # ------------------------------------------------------------------------- + # -- Detail-View this_assembly = get_object_or_404(Assembly, pk=pk) - - num_rows = this_assembly.cells.aggregate(mx=Max("row_number"))["mx"] - num_cols = this_assembly.cells.aggregate(mx=Max("column_number"))["mx"] - + layers: list[Layer] = this_assembly.get_ordered_layers() + if not layers: + new_layer = Layer.objects.create(assembly=this_assembly, thickness=1.0) + this_assembly.layer_id_order.append(new_layer.id) + this_assembly.save() + layers = this_assembly.get_ordered_layers() assembly_html = render_to_string( "webportal/partials/assemblies/assembly.html", - { + context={ "assembly": this_assembly, - "cells": this_assembly.cells.all(), - "row_range": range(num_rows), - "col_range": range(num_cols), + "layers": layers, }, ) - # sidebar_html = render_block_to_string( - # "webportal/partials/assemblies/sidebar_list.html", - # block_name="", - # context=Context( - # { - # "assemblies": Assembly.objects.filter(user=request.user), - # "active_assembly_id": pk, - # } - # ), - # request=request, - # ) - sidebar_html = render_to_string( - "webportal/partials/assemblies/sidebar_list.html", - { - "assemblies": Assembly.objects.filter(user=request.user), - "active_assembly_id": pk, - }, + # ------------------------------------------------------------------------- + # -- Sidebar + sidebar_html = render_block_to_string( + "webportal/partials/assemblies/sidebar.html", + block_name="assembly-sidebar-list", + context=Context( + { + "assemblies": Assembly.objects.filter(user=request.user), + "active_assembly_id": pk, + } + ), + request=request, ) return HttpResponse(assembly_html + sidebar_html, content_type="text/html") @@ -291,7 +288,7 @@ def update_assembly_name(request: WSGIRequest, pk: int) -> HttpResponse: this_assembly.save() template_name = "webportal/partials/assemblies/assembly.html" - context = {"assembly": this_assembly} + context = Context({"assembly": this_assembly}) detail_view_name = render_block_to_string( template_name, block_name="assembly-name", context=context, request=request ) @@ -312,71 +309,51 @@ def add_new_assembly(request: WSGIRequest) -> HttpResponse: def delete_assembly(request: WSGIRequest, pk: int) -> HttpResponse: this_assembly = get_object_or_404(Assembly, pk=pk) this_assembly.delete() - return assembly(request, Assembly.objects.filter(user=request.user).first().pk) - - -# --------------------------------------------------------------------------------------- -# -- Assembly-Detail-Views - - -def assembly_detail(request, pk): - container = get_object_or_404(Assembly, id=pk) - cells = container.cells.all() - max_x = cells.aggregate(max_x=models.Max("x"))["max_x"] or 0 - max_y = cells.aggregate(max_y=models.Max("y"))["max_y"] or 0 - row_range = range(max_y + 1) - col_range = range(max_x + 1) - context = { - "container": container, - "cells": cells, - "row_range": row_range, - "col_range": col_range, - } - return render( - request, - "grid.html", - context, + return assembly( + request, getattr(Assembly.objects.filter(user=request.user).first(), "pk", None) ) -def add_row(request, pk): - container = get_object_or_404(Assembly, id=pk) - max_row_num = container.cells.aggregate(mx=Max("row_number")).get("mx", 0) - new_row_num = max_row_num + 1 - max_col_num = container.cells.aggregate(mx=Max("column_number")).get("mx", 0) - - # Add new row - new_cells = [] - for col_num in range(max_col_num): - cell = Cell( - container=container, - column_number=col_num + 1, - row_number=new_row_num, - value="", - ) - new_cells.append(cell) - Cell.objects.bulk_create(new_cells) - - # Render just the new row - return HttpResponse( - render_to_string("webportal/partials/assemblies/row.html", {"cells": new_cells}) +@login_required +def add_layer(request: WSGIRequest, pk: int) -> HttpResponse: + this_assembly = get_object_or_404(Assembly, id=pk) + new_layer = Layer(assembly=this_assembly, thickness=1.0) + new_layer.save() + this_assembly.layer_id_order.append(new_layer.id) + this_assembly.save() + + layer_html = render_block_to_string( + "webportal/partials/assemblies/assembly.html", + block_name="layer", + context=Context( + { + "layer": new_layer, + "assembly": this_assembly, + } + ), + request=request, ) + return HttpResponse(layer_html, content_type="text/html") + +@login_required +def delete_layer(request: WSGIRequest, assembly_pk: int, layer_pk: int) -> HttpResponse: + this_assembly = get_object_or_404(Assembly, id=assembly_pk) + this_assembly.delete_layer(layer_pk) + this_layer = get_object_or_404(Layer, id=layer_pk) + this_layer.delete() -def add_column(request, pk): - container = get_object_or_404(Assembly, id=container_id) - max_row_num = container.cells.aggregate(mx=Max("row_number")).get("mx", 0) - max_col_num = container.cells.aggregate(mx=Max("column_number")).get("mx", 0) - new_col_num = max_col_num + 1 + return assembly(request, assembly_pk) - # Add new column - new_cells = [] - for row_num in range(max_row_num + 1): - cell = Cell( - container=container, column_number=new_col_num, row_number=row_num, value="" - ) - new_cells.append(cell) - Cell.objects.bulk_create(new_cells) - # Render just the new column - return JsonResponse({"html": render_to_string("column.html", {"cells": new_cells})}) +@login_required +@require_POST +def update_layer_thickness( + request: WSGIRequest, assembly_pk: int, layer_pk: int +) -> HttpResponse: + layer = get_object_or_404(Layer, id=layer_pk) + if request.method == "POST": + if thickness := request.POST.get("thickness"): + layer.thickness = float(thickness) + layer.save() + return HttpResponse(layer.thickness)