user editable dynamic html table inside form

I’d like to let the user edit a table inside the create view and also the update view and display it in the detail view. The table is the ‘distancePressures’ variable in the ‘Linker’ model. They should be able to insert rows, delete rows and edit the values in each column (distance, pressure).

Ideally I’d then like to have a dropdown with a few example values for the table, e.g. high resistance, medium resistance and low resistance, which be read into the table as initial values. The other values in the model, such as ‘startPressure’, use django_measurement to let the user set the input units, it would be nice to have the same ability to change the units used in the distance and pressure columns (but not row by row), so the user can enter values in psi and they would be converted in the model to MPa, but no conversion would be necessary if they entered in MPa directly.

The Linker class allows the ManyToMany relationship between the traveller and tube classes, with some extra properties. It started as just the ‘through’ parameter in an ManyToMany variable that initially was a member of Tube, then I wanted to have it as a proper model with views etc that can be created easily by the user.

I’m currently a bit stuck on this (new Django user, my first real project), and have spent a while reading about django-tables2, django-datatables, ajax and jQuery without getting anywhere. The guides I’ve found are either too focused on beginner problems, which were useful when I wanted to displaying a table of known size using <table> and <tr> etc but not now, or focus on advanced features like filters, sorting and pagination that aren’t related to this problem. Any help would be appreciated!

# models.py
# Used to  model link traveller and tube together
class Linker(models.Model):
    startPressure = MeasurementField(
        verbose_name = "Start Pressure",
        measurement = Pressure,
        unit_choices = (("MPa", "MPa"), ("Pa", "Pascals"), ("psi", "psi")),
    )

    # Guess this is an appropriate data structure, in normal python I'd use 
    #   distances = np.array...
    #   pressures = np.array...
    distancePressures = ArrayField(
        ArrayField(models.FloatField(null=True, blank=True)), size=2,
    )

    traveller = models.ForeignKey('traveller', on_delete=models.CASCADE)
    tube   = models.ForeignKey('tube', on_delete=models.CASCADE)

    @staticmethod
    def editable_field_names():
        return ('startPressure', 'distancePressures', 'traveller', 'tube')

    @staticmethod
    def display_field_names():
        return Linker.editable_field_names()

Views

# views.py
class LinkerCreateView(LoginRequiredMixin, CreateView):
    model = Linker
    template_name = 'inker/linker_create.html'
    login_url = 'account_login'
    success_url = reverse_lazy('linker_list')
    fields = Linker.editable_field_names()


class LinkerDetailView(LoginRequiredMixin, DetailView):
    model = Linker
    template_name = 'linker/linker_detail.html'
    login_url = 'account_login'
    context_object_name = 'linker'
    fields = Linker.display_field_names()

class LinkerUpdateView(LoginRequiredMixin, UpdateView):
    model = Linker
    template_name = 'linker/linker_update.html'
    login_url = 'account_login'
    success_url = reverse_lazy('linker_list')
    fields = Linker.display_field_names()

Create Form template

<!-- templates/linker/linker_create.html -->
{% extends '_base.html' %}
{% load crispy_forms_tags %}

{% block title %}{{ linker.name }}{% endblock title %}

{% block content %}
  <form method="post"> 
    {% csrf_token %}
     {{ form|crispy }}
    <button class="btn btn-success" type="submit">Save</button> 
  </form>

  <div class="card-footer text-center text-muted">
    <a href="{% url 'linker_list' %}">Back to Linkers</a>
  </div>
{% endblock content %}

Detail View template

<!-- templates/linker/linker_detail.html -->

{% extends '_base.html' %}

{% block title %}{{ linker.name }}{% endblock title %}

{% block content %}
  <div class="linker-detail"> 
    <h2><a href="">{{ linker.name }}</a></h2> 
    <p>Tube: <a href="{{ linker.tube.get_absolute_url }}">{{ linker.tube.name }}</a></p> 
    <p>Traveller: <a href="{{ linker.traveller.get_absolute_url }}">{{ linker.traveller.name }}</a></p> 
  </div>
  <div class="card-footer text-center text-muted">
    <a href="{% url 'linker_list' %}">Back to Linkers</a>   |
    <a href="{% url 'linker_update' linker.pk %}">Edit</a>  |
    <a href="{% url 'linker_delete' linker.pk %}">Delete</a>
  </div>
{% endblock content %}

Update View template

<!-- templates/linkers/linker_update.html -->
{% extends '_base.html' %}
{% load crispy_forms_tags %}

{% block title %}{{ linker.name }}{% endblock title %}

{% block content %}
  <h1>Edit</h1>
  <form method="post"> 
    {% csrf_token %}
     {{ form|crispy }}
    <button class="btn btn-success" type="submit">Save</button> 
  </form>

  <div class="card-footer text-center text-muted">
    <a href="{% url 'linker_list' %}">Back to Linkers</a>   |
    <a href="{% url 'linker_delete' linker.pk %}">Delete</a>
  </div>
{% endblock content %}

Base Template

<!-- templates/_base.html-->
{% load static %}
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>{% block title %}Artemis{% endblock title %}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <link rel="stylesheet" href="{% static 'css/base.css' %}">
</head>

<body>
  <header>
    <!-- Fixed navbar -->
    <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4
     mb-3 bg-white border-bottom shadow-sm">
      <a href="{% url 'home' %}" class="navbar-brand my-0 mr-md-auto
      font-weight-normal">Artemis</a>
      <nav class="my-2 my-md-0 mr-md-3">
        <a class="p-2 text-dark" href="{% url 'tube_list' %}">Tubes</a>
        <a class="p-2 text-dark" href="{% url 'traveller_list' %}">Travellers</a>
        <a class="p-2 text-dark" href="{% url 'linker_list' %}">Linkers</a>

        <a class="p-2 text-dark" href="{% url 'about' %}">About</a>
        {% if user.is_authenticated %}
          <a class="p-2 text-dark" href="{% url 'account_logout' %}">Log Out</a>
        {% else %}
          <a class="p-2 text-dark" href="{% url 'account_login' %}">Log In</a>
          <a class="btn btn-outline-primary"
            href="{% url 'account_signup' %}">Sign Up</a>
        {% endif %}
      </nav>
    </div>
  </header>
    <div class="container">
        {% block content %}
        {% endblock content %}
    </div>
    <!-- Javascript -->
    {% comment %} <script src="{% static 'js/base.js' %}"></script> {% endcomment %}
    <!-- JS, Popper.js, and jQuery -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
     <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js" integrity="sha384-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVJJAzGytlQcNXd" crossorigin="anonymous"></script>
</body>
</html>

If this is your first real project, I’d suggest setting your initial goals a little lower initially.

You’ve touched on a number of different topics / ideas here, and it may be a bit much to handle all at once.

I’m not suggesting you remove or change any of those objectives. What I’m suggesting is that you try to get there incrementally - take smaller steps while you learn and understand all the different pieces involved in doing what you’re trying to do.

Starting with your very first paragraph -

This is the purpose of Django’s Formsets. Since that’s at the top of your list, that’s what I’d suggest you start with.

Create your view (and the associated JavaScript) that builds and works with your forms.

Once you’ve got a grasp on the fundamentals there, then you can add on your various desired enhancements.

If you try to do this all at once, you may have a difficult time getting all the pieces working together, or may end up not understanding what each of those components are doing as part of the overall process.

1 Like