Accessing fields from foreign key in inlineformset

I have the following models and formset:

from django.utils.timezone import localtime
from django.conf import settings
from django.db import models
# Create your models here.


class Meal(models.Model):
    time = models.DateTimeField("Tid/Dato")
    description = models.CharField(verbose_name="Beskrivelse af måltid", max_length=300)
    deadline = models.DateTimeField(verbose_name="Tilmeldingsfrist")
    price_pp = models.DecimalField(verbose_name="Pris per person", decimal_places=2, max_digits=5)
    price_tt = models.DecimalField(verbose_name="Pris i alt", decimal_places=2, max_digits=5)
    resp = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="meal_responsibilities")
    offer_takeaway = models.BooleanField(verbose_name="Tilbyd takeaway", default=False)
    offer_eat_in = models.BooleanField(verbose_name="Tilbyd spisning i netværket", default=False)
    participants = models.ManyToManyField(settings.AUTH_USER_MODEL, through="Participant")

    def __str__(self):
        return (f"{self.description} Dato:"
                f" {localtime(self.time).date().strftime("%d/%m/%Y")} "
                f"{localtime(self.time).strftime("%H:%M")} Pris: {self.price_pp}")

    def weekday(self):
        weekdays = ["Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"]
        index = self.time.date().weekday()

        return weekdays[index]

    def resp_name(self):
        return self.resp.name


class Participant(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name="Bruger", on_delete=models.CASCADE)
    meal = models.ForeignKey(Meal, verbose_name="Måltid", on_delete=models.CASCADE)
    takeaway = models.BooleanField(verbose_name="Takeaway", default=False)
    eat_in = models.BooleanField(verbose_name="Spisning i netværket", default=False)
    registration_time = models.DateTimeField(verbose_name="Date/time registered")
    payment_due = models.DateField(verbose_name="Betales senest")
    payment_status = models.BooleanField(verbose_name="Er betalt", default=False)

    def registered_on_time(self):
        meal_record = self.meal
        return self.registration_time <= meal_record.deadline

    def __str__(self):
        return f"Bruger: {self.user}  Dato: {localtime(value=self.meal. time).strftime("%x kl. %H:%M")}"


class AltidenUser(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    phone = models.CharField(max_length=8)
    is_ks = models.BooleanField(verbose_name="Is kitchen staff", default=False)
    is_mu = models.BooleanField(verbose_name="is meal user", default=False)
    is_staff = models.BooleanField(verbose_name="is staff", default=False)

from .models import Participant, Meal
from django.forms import inlineformset_factory

ParticipantFormSet = inlineformset_factory(Meal, Participant,
                                           fields={"eat_in", "takeaway"},
                                           extra=0,
                                           labels={
                                               "eat_in": "Spis i Netværket",
                                               "takeaway": "Takeaway"
                                           },
                                           )


What I’m looking to do is having an authenticated user select the options available (eat in, takeaway or both) from a list of meals. So I’ve made an inline form set modifying the Participant table with Meal as the foreign key. How do I do this in the template? That is, make a list of meals with checkboxes to sign up the options the meal offers?

Best, Mads

I’m not sure I understand what you’re asking for here.

You render an inlineformset the same way you render any other formset.

If you need to customize how a particular field is being rendered, you can create a custom form to be used for that model, and specify that that form is to be used in the formset.

Since you’ve defined those fields (eat_in and takeaway) as BooleanField, by default, they’re going to render as checkboxes.

If this isn’t addressing what you’re asking, then I think I’m going to need more detail about what the issue is that you need help with.

Hi, thanks for your reply :slight_smile:

When I loop over the formset in the template, I need a way to access the fields for the Meal, corresponding to the meal being signed up for so the user knows which meal they are signing up for.

Best, Mads

If you’ve created a formset for Meal, then rendering the formset should render all the fields for Meal that you have defined for each instance of Meal.

I have the formset mentioned above, modifying the Participant model for the users to register, I’d like to show the fields for Meal besides the form so they know which meal they are registering.

Can you provide a mock-up or an image of what it is you’re trying to create? And then along with that, identify the source for each field as it relates to that form.

Here is a list of Meal Records, with “Tilbud” meaning the options available and “Tilmeld” meaning “sign up”, where the user is meant to check the options they want to sign up for and have the record saved in the Participant model when they clicl the “tilmeld” button (“sign up”)

Best, Mads

Fantastic!

To confirm my understanding, each of the 4 rows shown, are individual instances of a Meal? The area circled in red, labeled “1” is one Meal?

What determines which instances of Meal to show here? (Or are all instances displayed?)

Now, on the right side, I see three different combinations, two of which I have circled and labeled “2” and “3”.

What determines which check boxes are supposed to show up here? (What values in what fields? Please be very specific here.)

When this form is submitted, where are the values in those fields to be stored? (Again, be specific regarding which model and fields.)

Yes, “1” is one meal.
All records are displayed, but I plan on only showing those meal whose deadlines are in the future.

Meal has boolean fields offer_eat_in and offer_takaway which determines which which checkboxes are shown (shown if True, not otherwise).

When the form is submitted, the results are to be stored in the Participant model along with the, User ID, Meal ID, registration time, payment date (the first of the following month) and the options regarding takeaway and/or eat in.

Best, Mads

Ok, we’re getting closer. I think there’s an issue here in that you’re rendering instances of Meal that are not related to a Participant. (Each Participant is only related to one meal.)

In this case, an inline formset would not be your solution here.

At best, this should be a regular formset of Meal, where your render the data along with conditional rendering of those two checkboxes.

Which mechanism would be best suited to display those checkboxes? I don’t see how a formset of Meals would help, since it is not the Meal model I want to modify.

Best, Mads.

Either:

  • Render the individual fields, surrounded by {% if ... %} tags to determine whether that particular checkbox needs to be rendered

  • Modify the instance of the form to remove an unused field

I probably could have phrased that better. It would be a regular formset with the individual forms based on the instances of Meal being rendered for that page.

The effective difference between an inline formset and a regular formset is similar to the difference between a Form and a ModelForm. A regular form has no specific association with a Model. Likewise, a formset is just a data structure for managing a set of identical forms.

What you do with that data submitted by the form is up to you.

How would one do this?

Best, Mads

To best / most properly answer that question, I’d need to see the view generating that page.

This is what I have written so far (with help from IRC)

from django.shortcuts import render
# from django.http import HttpResponse
# from django.template import loader
from .models import Meal, Participant
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from datetime import timedelta
from django.db.models import FilteredRelation, Q, F
from .forms import ParticipantFormSet



# Create your views here.


@login_required
def index(request):

    if request.POST:
        f = ParticipantFormSet(request.POST)
        # dt = timezone.now()
        # pd = (dt.replace(day=1) + timedelta(days=32)).replace(day=1)

        print(f.is_valid())
        print(f.errors)

    current_user = request.user
    
    # Annotate the meal object if the current user has registered for takeaway or eat in
    qs = Meal.objects.annotate(
        _participant=FilteredRelation(
            'participant',
            condition=Q(participant__user=current_user)
        )
    ).annotate(
        user_eat_in=F('_participant__eat_in'),
        user_takeaway=F('_participant__takeaway')
    )

    qs = qs.order_by('time')

    formset = ParticipantFormSet(queryset=qs)
    return render(request, "madliste/index.html",
                  {'user': current_user, 'formset': formset})

I have been looking at this, and I realized there are two different situations here.

Do the Participant objects already exist at the time this view is being called? Or is there the possibility that this view needs to create those Participant instances?

I actually did not know this was/could be an issue, so yes, there is a possibility that the Participant instances need to be created.

It will affect the forms being created. A formset based on a model will only create a form for those model instances, plus whatever is defined for extra. So if you’ve got an instance of Meal for which no instance of Participant referring to that Meal for the current user, The formset won’t create a form for it.

I see, how can I proceed?

Actually, as I continue to look at this and think about it, another question came to mind.

Is it valid for a Participant to have both eat_in and takeaway selected? I know I don’t understand the process that you’re trying to model, but superficially, it appears to me like those might be mutually-exclusive selections.

If they are, is there any reason why you have them defined in the Participant model as separate Boolean fields instead of (say) a CharField with defined choices?