Django - Adding a 2nd form to a page dynamically

I'm looking for guidance on creating a dynamic form in Django that changes depending on which participants are in a meeting.

The background is as follows: The app is used to track meeting scores across several teams, each with 4-8 members. Over the course of a few days a team may have 6 meetings. For each meeting the Team will elect 1 or 2 members to be meeting Participants. Each meeting requires the team to put forward members such that over the course of the training all team members have participated in one or more meetings. For each meeting all other team members are "Observers".

The challenge I'm facing is how to dynamically insert a form or form fields for each of the Observers given that the number of observers may be anything from none to 6. The idea is to allow Observers to "rate" the meeting and record this result in the Observer model.

The ScoreCreate view correctly sets the 1st participant and populates the "Other Participant" dropdown with all other team members.

HTMX in the template passes the 1st and if selected "other participant" to the observers function which is then able to sort out who the observers should be.

What I'm not able to figure out is how to generate and insert a form that is pre-populated with each of the Observers and allows for input of a score( Name of Observer, field: rating), given the number of Observers will vary depending on the number of team members and whether or not there are two participants in the meeting.

Thoughts on how I might approach this are welcome.

MODELS:

from .choices import SCORE_CHOICES, RATING_CHOICES

class Meeting(models.Model)
    date = models.DateTimeField(auto_now=True)

class Team(models.Model):
    # up to 8 members per team
    name = model.CharField(max_length=25)


class Participant(models.Model):
    name = model.CharField(max_length=25)
    team = model.ForeignKey(Team, on_delete.models.CASCADE)


class Score(models.Model):
# primary is required, secondary is not required
    meeting = models.ForeignKey('Meeting', on_delete.models.CASCADE)
    primary_participant = models.ForeignKey('Participant', on_delete.models.CASCADE)
    secondary_participant = models.ForeignKey('Participant', related_name='secondary_participant', on_delete.model.CASCADE, blank=True, null=True)
    score = models.CharField(max_length=25, choices = SCORE_CHOICES)


class Observer(models.Model):
    score = models.ForeignKey(Score, on_delete.models.CASCADE)
    observer = models.ForeignKey(Participant, on_delete.models.CASCADE)
    rating = models.CharField(max_length=1, choices=RATING_CHOICES)

VIEWS:

class ScoreCreate(LoginRequiredMixin, CreateView):
    model = Score
    form_class = ScoreCreateForm
    template_name = 'score_create.html'

    def get_initial(self):
        participants = Participant.objects.filter(slug=self.kwargs['slug'])
        participant_id = self.kwargs['pk']
        other_participants = participants.filter(~Q(pk=self.kwargs['pk'])
    

def observers(request):
    participant = request.GET.get('participant')
    other_participant = request.GET.get('other_participant')
    team = request.GET.get('team')

    if other_participant:
        observers = Participant.objects.filter(~Q(id=participant) & ~Q(id=other_participant) & Q(team=team))

    else:
        observers = Participant.objects.filter(~Q(id=participant) & Q(team=team) & Q(role='PARTICIPANT'))

    return render(request, 'trial/partials/all_observers.html', context={'observers': observers})       

TEMPLATE: score.html

<form method="POST" name="score_participants">
<div class="d-flex">
    <div class="align-self-end g-color-primary text-uppercase g-letter-spacing-1 g-font-weight-800 g-my-10">Meeting Details
    </div>
    <div class="ml-auto">
        <input class="btn btn-md btn-warning g-mr-10 g-mb-15 g-rounded-50" onclick="location.href='#'" type="button"
               value="Cancel">
        <button class="btn btn-md u-btn-primary g-mr-0 g-mb-15 g-rounded-50" type="submit">Submit</button>
    </div>
</div>
<div class="d-flex g-mt-0 g-brd-top g-brd-gray-light-v3 g-bg-secondary">
    <div class="g-mt-12 g-ml-10">Participant:</div>
    <div>
        {{form.participant|as_crispy_field}}
    </div>
    <div class="ml-auto g-mt-13 g-mb-12 g-mr-10 g-color-primary text-uppercase g-letter-spacing-1 g-font-weight-600">{{participant}}</div>
</div>

<div class="d-flex g-mt-0 g-brd-top g-brd-gray-light-v3">
    <div class="g-mt-18 g-ml-10 g-mr-50">Meeting Number:</div>
    <div class="ml-auto g-mt-13 g-mr-10">{{form.meeting_number|as_crispy_field}}</div>
<div class="d-flex g-mt-0 g-brd-top g-brd-gray-light-v3 g-bg-secondary">
    <div class="g-mt-18 g-ml-10 g-mr-50">Name of other meeting participant:</div>
    <div class="ml-auto g-mt-13 g-mr-10"
        hx-get="{% url 'observers' %}"
         hx-include="[name='score_participants']"
        hx-trigger="change"
        hx-target = "#observers">
            {{form.other_participant|as_crispy_field}}
        </div>
    </div>

<div class="g-mt-0 g-brd-top g-brd-gray-light-v3">
    <div id="observers"></div>
</div>
...

TEMPLATE: observer.html

{% block content %}
    {% for observer in observers %}
<div class="d-flex">
    <div>{{observer}}</div>
    <div class="ml-auto">RATING (RADIO BUTTONS)</div>
</div>
{% endfor %}
{% endblock %}

Example with 1 participant

Example with 2 participants

The idea is to use the view that handles that data coming from that page you've shown going to the next page that shows the forms. So if you are just trying to show the forms on the next page you can handle the logic within your view and say if there are 4 observers then you will want to have a for loop that creates a form for each of them and appends them to a list. Then in HTMX you have for each in that list, print that form out, and you can use id's on each html object in forms.py and manage them in css.

If you are looking to save this model for later use. You essentially want to do the same thing but instead of passing those values along URL(managing the parameters in a view) you can just use them straight from the database. In fact, this would simply work in either situation with that small caveat that using more database calls will slow the site down.

Back to Top