How to create a "Bulk Edit" in Django?

I would like to create a bulk edit for my Django app, but the problem is that I don't get the function to work. I got lost at some point while creating it, and don't know what I am doing wrong. I feel sruck at this point, I don't see other options. Here is what I have done so far (I am using airtable as the database):

The error is mostly when it tryies to retrieve the products from the tables after being selected with the checkbox (I tried to debug it, and change it but I don't see what else I could do)

models.py

from django.db import models

class RC(models.Model):
    sku = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    cost = models.DecimalField(max_digits=10, decimal_places=2)
    weight = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.name

class Category(models.Model):
    sku = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=255)
    category = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.name

class LB(models.Model):
    sku = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=255)
    cost = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.name

class PM(models.Model):
    sku = models.CharField(max_length=50, unique=True)
    name = models.CharField(max_length=255)
    cost = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.name

views.py

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from .models import RC, LB, PM, Category
from django.http import HttpResponse
from django.contrib import messages
from pyairtable import Table
from django.core.exceptions import ObjectDoesNotExist

API_KEY = My airtable api
BASE_ID = My airtable app id

RC_table = Table(API_KEY, BASE_ID, 'RC')
LB_table = Table(API_KEY, BASE_ID, 'L&B')
PM_table = Table(API_KEY, BASE_ID, 'PM')
Category_table = Table(API_KEY, BASE_ID, 'Category')

def product_list(request):
    rc_products = RC_table.all()
    lb_products = LB_table.all()
    pm_products = PM_table.all()
    category_data = Category_table.all()

    category_dict = {category['fields'].get('Name', 'Unknown'): category['fields'].get('Category', 'Undefined')
    for category in category_data}

    combined_products = []

    table_filter = request.GET.get('table_filter', 'all')

    for record in rc_products:
        fields = record['fields']
        category = category_dict.get(fields.get('Name', ''), 'N/A')
        combined_products.append({
            'id': record['id'],
            'sku': fields.get('SKU', ''),
            'name': fields.get('Name', ''),
            'cost': fields.get('Cost', 0),
            'price': fields.get('Price', 0),
            'weight': fields.get('Weight (Lbs)', 0),
            'profit': fields.get('Profit', 0),
            'margin': fields.get('Margin', 0),
            'category': category,
            'table_name': 'RenoCart',
        })

    for record in lb_products:
        fields = record['fields']
        combined_products.append({
            'id': record['id'],
            'sku': fields.get('SKU', ''),
            'name': fields.get('Name', ''),
            'cost': fields.get('Cost', 0),
            'price': None,  # L&B does not have a Price field
            'weight': None,  # L&B does not have a Weight field
            'table_name': 'L&B',
        })

    for record in pm_products:
        fields = record['fields']
        combined_products.append({
            'id': record['id'],
            'sku': fields.get('SKU', ''),
            'name': fields.get('Name', ''),
            'cost': fields.get('Cost', 0),
            'price': None,  # PM does not have a Price field
            'weight': None,  # PM does not have a Weight field
            'table_name': 'Pont-Masson',
        })

    if table_filter == 'RenoCart':
        combined_products = [product for product in combined_products if product['table_name'] == 'RenoCart']
    elif table_filter == 'Pont-Masson':
        combined_products = [product for product in combined_products if product['table_name'] == 'Pont-Masson']
    elif table_filter == 'L&B':
        combined_products = [product for product in combined_products if product['table_name'] == 'L&B']
    else:
        combined_products = combined_products

    search_query = request.GET.get('search', '')
    if search_query:
        combined_products = [
            product for product in combined_products
            if search_query.lower() in product['name'].lower()
        ]

    combined_products = sorted(combined_products, key=lambda x: x['name'], reverse=False)

    context = {
        'products': combined_products,
        'search_query': search_query,
        'table_filter': table_filter,
    }
    return render(request, 'inventory/product_list.html', context)
    

def bulk_edit_products(request):
    if request.method == 'POST':

        selected_ids = request.POST.getlist('selected_products')
        
        print(f"Selected product IDs for bulk edit: {selected_ids}")

        if not selected_ids:
            return HttpResponse("No products selected for bulk edit.", status=400)

        products = []
        for product_id in selected_ids:
            product = None
            print(f"Looking for product with sku: '{product_id}'") 

            for model, table_name_in_code in [(RC, 'RenoCart'), (LB, 'L&B'), (PM, 'Pont-Masson')]:
                try:
                    product = model.objects.get(sku=product_id)
                    print(f"Found product: '{product.name}' in {table_name_in_code}")  
                    product.table_name = table_name_in_code
                    products.append(product)
                    break
                except ObjectDoesNotExist:
                    print(f"Product with sku '{product_id}' not found in {table_name_in_code}")
                    continue

        if not products:
            return HttpResponse("No valid products found for bulk edit.", status=400)

        context = {
            'products': products,
        }

        return render(request, 'inventory/bulk_edit_products.html', context)

    selected_ids = request.GET.getlist('selected_products')
    if not selected_ids:
        return HttpResponse("No products selected for bulk edit.", status=400)

    products = []
    for product_id in selected_ids:
        print(f"Looking for product with sku: '{product_id}'") 

        product = None
        for model, table_name in [(RC, 'RenoCart'), (LB, 'L&B'), (PM, 'Pont-Masson')]:
            try:
                product = model.objects.get(sku=product_id) 
                print(f"Found product: '{product.name}' in {table_name}")
                product.table_name = table_name
                products.append(product)
                break
            except ObjectDoesNotExist:
                print(f"Product with sku '{product_id}' not found in {table_name}")
                continue

    if not products:
        return HttpResponse("No valid products found for bulk edit.", status=400)

    context = {
        'products': products,
    }

    return render(request, 'inventory/bulk_edit_products.html', context)

bulk_edit_products.html

{% block content %}
<h1>Bulk Edit Products</h1>

<form method="POST" action="{% url 'bulk_edit_products' %}">
    {% csrf_token %}
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>SKU</th>
                <th>Name</th>
                <th>Cost</th>
                <th>Price</th>
                <th>Weight (Lbs)</th>
            </tr>
        </thead>
        <tbody>
            {% for product in products %}
                <tr>
                    <!-- SKU: Read-only for RenoCart products, editable for others -->
                    <td>
                        {% if product.table_name == 'RenoCart' %}
                            <input type="text" name="sku_{{ product.id }}" value="{{ product.sku }}" readonly class="form-control">
                        {% else %}
                            <input type="text" name="sku_{{ product.id }}" value="{{ product.sku }}" class="form-control">
                        {% endif %}
                    </td>
                    <td><input type="text" name="name_{{ product.id }}" value="{{ product.name }}" class="form-control"></td>
                    
                    <!-- Cost: Read-only for L&B and Pont-Masson, editable for RenoCart -->
                    {% if product.table_name == 'RenoCart' %}
                        <td><input type="text" name="cost_{{ product.id }}" value="{{ product.cost }}" class="form-control"></td>
                    {% else %}
                        <td><input type="text" name="cost_{{ product.id }}" value="{{ product.cost }}" readonly class="form-control"></td>
                    {% endif %}
                    
                    <!-- Price: Read-only for L&B and Pont-Masson, editable for RenoCart -->
                    {% if product.table_name == 'RenoCart' %}
                        <td><input type="text" name="price_{{ product.id }}" value="{{ product.price }}" class="form-control"></td>
                    {% else %}
                        <td><input type="text" name="price_{{ product.id }}" value="{{ product.price }}" readonly class="form-control"></td>
                    {% endif %}
                    
                    <!-- Weight: Read-only for L&B and Pont-Masson, editable for RenoCart -->
                    {% if product.table_name == 'RenoCart' %}
                        <td><input type="text" name="weight_{{ product.id }}" value="{{ product.weight }}" class="form-control"></td>
                    {% else %}
                        <td><input type="text" name="weight_{{ product.id }}" value="{{ product.weight }}" readonly class="form-control"></td>
                    {% endif %}
                </tr>
            {% endfor %}
        </tbody>
    </table>
    
    <button type="submit" class="btn btn-primary">Save Changes</button>
</form>
{% endblock %}

products_list.html

{% block content %} 
<h1 class="text-center" style="font-size: 3rem; font-weight: 600; color: #004080; margin-top: 1px; margin-left: 830px">
    Database
</h1>

{% if user.is_authenticated %}
    <a href="{% url 'user_logout' %}" 
       style="position: absolute; top: 20px; right: 20px; text-decoration: none; 
              background-color: #004080; color: white; padding: 10px; 
              border-radius: 4px; text-align: center; width: 80px;">
       Logout
    </a>
    <a href="{% url 'add_product' %}" style="display: block; margin-bottom: 10px; text-decoration: none; background-color: #004080; color: white; padding: 10px; border-radius: 4px; text-align: center; width: 150px;">Add Product</a>
{% endif %}

<link rel="stylesheet" href="https://cdn.datatables.net/1.13.1/css/jquery.dataTables.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.1/js/jquery.dataTables.min.js"></script>

<script>
$(document).ready(function() {
    $('#product-table').DataTable({
        paging: true,
        searching: true,
        ordering: true,
        info: true,
        responsive: true,
        pageLength: 25,
        lengthMenu: [5, 10, 25, 50, 100]
    });
});
</script>

<form method="get" action="{% url 'product_list' %}" class="mb-4">
    <div class="input-group">
        <select name="table_filter" class="form-select">
            <option value="all" {% if table_filter == 'all' %}selected{% endif %}>All Stores</option>
            <option value="Pont-Masson" {% if table_filter == 'Pont-Masson' %}selected{% endif %}>Pont-Masson</option>
            <option value="RenoCart" {% if table_filter == 'RenoCart' %}selected{% endif %}>RenoCart</option>
            <option value="L&B" {% if table_filter == 'L&B' %}selected{% endif %}>L&B</option>
        </select>
        <button type="submit" class="btn btn-primary">Filter</button>
    </div>
</form>

<button class="btn btn-sm btn-primary mb-2" id="toggle-category">
    Toggle Category
</button>

<form method="POST" action="{% url 'bulk_edit_products' %}">
    {% csrf_token %}
    <button type="submit" class="btn btn-primary mt-2">Bulk Edit</button>
    <table id="product-table" class="display" style="width: 100%; border-collapse: collapse; margin-top: 20px;">
        <thead>
            <tr style="background-color: #002F6C; color: white;">
                <th>Select</th>
                <th>SKU</th>
                <th>Name</th>
                <th>Cost</th>
                <th>Price</th>
                <th>Profit</th>
                <th>Margin</th>
                <th>Weight (Lbs)</th>
                <th class="category-column"> Category</th>
                <th>Store</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            {% for product in products %}
                <tr>
                    <td> <input type="checkbox" name="selected_products" value="{{ product.id }}"> </td>
                    <td>{{ product.sku }}</td>
                    <td>{{ product.name }}</td>
                    <td>
                        {% if product.cost %}
                            ${{ product.cost|floatformat:2 }}
                        {% else %}
                            N/A
                        {% endif %}
                    </td>
                    <td>
                        {% if product.price %}
                            ${{ product.price|floatformat:2 }}
                        {% else %}
                            N/A
                        {% endif %}
                    </td>
                    <td>
                        {% if product.profit %}
                            ${{ product.profit|floatformat:2 }}
                        {% else %}
                            N/A
                        {% endif %}
                    </td>
                    <td>
                        {% if product.margin %}
                            {{ product.margin|floatformat:2 }}%
                        {% else %}
                            N/A
                        {% endif %}
                    </td>
                    <td>
                        {% if product.weight %}
                            {{ product.weight|floatformat:2 }}
                        {% else %}
                            N/A
                        {% endif %}
                    </td>
                    <td class="category-column">{{ product.category|safe }}</td>
                    <td>{{ product.table_name }}</td>
                    <td>
                        <a href="{% url 'update_product' product.id product.table_name %}" class="btn btn-warning btn-sm" style="color: #004080; text-decoration: none; margin-right: 5px;">Edit</a> |
                        <a href="{% url 'delete_product' product.id product.table_name %}" class="btn btn-danger btn-sm" style="color: #d9534f; text-decoration: none;">Delete</a>
                    </td>
                    <script>
                        document.addEventListener("DOMContentLoaded", function () {
                            const toggleButton = document.getElementById("toggle-category");
                            const categoryHeader = document.querySelector(".category-column");
                            const categoryCells = document.querySelectorAll("td.category-column");

                            toggleButton.addEventListener("click", function () {
                                // Check current display state
                                const isHidden = categoryHeader.style.display === "none";

                                // Toggle header and cells
                                categoryHeader.style.display = isHidden ? "" : "none";
                                categoryCells.forEach(cell => {
                                    cell.style.display = isHidden ? "" : "none";
                                });

                                // Update button text
                                toggleButton.textContent = isHidden ? "Hide Category" : "Show Category";
                            });

                            // Initialize column as hidden
                            categoryHeader.style.display = "none";
                            categoryCells.forEach(cell => (cell.style.display = "none"));
                            toggleButton.textContent = "Show Category";
                        });
                    </script>
                </tr>
            {% endfor %}
        </tbody>
    </table>
</form>

<script>
function confirmDelete(productId, table) {
    if (confirm('Are you sure you want to delete this product?')) {
        window.location.href = `/products/delete/${productId}/${table}/`;
    }
}
</script>

{% endblock %}
Back to Top