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 %}