Search code examples
pythondjangopython-3.xdjango-formsdjango-tables2

display a table showing model instances as rows and let users select instances (rows) to delete


In my app, users can upload files. These files are represented by a model with attributes for associated meta-data (upload time, file name, user created note, max value, etc).

A user will upload several files and I want to show them a table of their uploaded files with a checkbox next to each row that can be used to delete selected files and associated model instances.

I'm not sure what the right approach is, I've looked at the following options but there doesn't seem to be an obvious solution:

  1. model forms, using CheckboxSelectMultiple

  2. django_tables2 - seems to be an established 3rd party app

  3. reuse the django admin code (form and view).

The default django admin app behavior is perfect for my use case, but I'm not sure what the best way is to reproduce it?

app/models.py

import uuid

from django.contrib.auth import get_user_model
from django.db import models

User = get_user_model()


class Document(models.Model):
    def rename_file(self, filename):
        ext = filename.split('.')[-1]
        new_name = uuid.uuid4().hex

        return f'documents/{new_name}.{ext}'

    owner = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        editable=True,
    )
    document = models.FileField(upload_to=rename_file)
    notes = models.CharField(max_length=258, blank=True)
    uploaded_at = models.DateTimeField(auto_now_add=True)
    nice_name = models.CharField(max_length=128, null=True, blank=False)
    start_date = models.DateField(null=True, blank=False)
    end_date = models.DateField(null=True, blank=False)

    def __str__(self):
        return str(self.document)

app/admin.py

from django.contrib import admin

from .models import Document


def uuid(obj):
    return obj.owner.uuid


uuid.short_description = 'user id'


@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
    fields = ('document', 'notes')
    list_display = (
        'owner',
        uuid,
        'document',
        'nice_name',
        'notes',
        'uploaded_at',
        'start_date',
        'end_date'
    )

app/forms.py

from django import forms
from django.forms.widgets import CheckboxSelectMultiple

from .models import Document


class DeleteDocumentsForm(forms.ModelForm):

    document = forms.ModelMultipleChoiceField(queryset=None, widget=forms.CheckboxSelectMultiple)
    nice_name = forms.ModelMultipleChoiceField(queryset=None, widget=forms.CheckboxSelectMultiple)


    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        qs = Document.objects.filter(owner_id=user)
        #  super(DeleteDocumentsForm, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)
        self.fields['document'].queryset = qs
        self.fields['nice_name'].queryset = qs.values('nice_name')

    #  document.fields['nice_name'].widget.attrs['readonly'] = True

    class Meta:
        model = Document
        fields = ('document', 'nice_name')
        widgets = {
            'document': CheckboxSelectMultiple,
            'nice_name': CheckboxSelectMultiple,
        }

Solution

  • To achieve what you want to do, you need to create a form that encapsulates a table. When the for loop iterates over each item from your database, provide a checkbox which value would be the unique ID of the uploaded file. By submitting the form, you would also then be submitting these IDs.

    So this would be your view:

    <form action="." method="post">
       {% csrf_token %}
       <table>
          {% for upload in uploads %}
             <tr>
                <td><input type="checkbox" name="selected" value="{{ upload.id }}"></td>
                <td><img src="{{ upload.url }}" alt="image"></td>
                <td>{{ upload.name }}</td>
              </tr>
           {% endfor %}
       </table>
       <button type="submit">Delete</button>
    </form>
    

    Once the user has selected one or multiple checkboxes, and after pressing on delete, you will receive a query dict with the IDs of the selected items that you'll be able to process as you want in your view:

    <QueryDict: {'csrfmiddlewaretoken': [''], 'selected': ['2', '3']}>
    

    As for the form, I don't really see any use of it IMO.