An other way to manage Django Model instance from ModelChoiceField in formset

153 Views Asked by At

I'm not sure if it's possible. I'm trying to use Django (2.0) Form and Formset on a view. Here is my models:

from uuid import uuid4

class Aliment(models.Model):
    id = models.CharField(max_length=36, primary_key=True, unique=True, default=uuid4, editable=False)
    libelle = models.CharField(max_length=50)
    def __str__(self):
        return self.libelle

class Menu(models.Model):
    id = models.CharField(max_length=36, primary_key=True, unique=True, default=uuid4, editable=False)
    libelle = models.CharField(max_length=50)
    aliments = models.ManyToManyField(Aliment, through='Ingredient')
    def __str__(self):
        return self.libelle

class Ingredient(models.Model):
    id = models.CharField(max_length=36, primary_key=True, unique=True, default=uuid4, editable=False)
    aliment = models.ForeignKey(Aliment, on_delete=models.CASCADE)
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    quantite = models.IntegerField(default=0)
    def __str__(self):
        return "({0}:{1})".format(self.quantite, self.aliment)

I'm using Formset to handle ('aliment', 'quantite') from Ingredient. Here is my forms:

class MenuModelForm(forms.ModelForm):
    class Meta:
        model = Menu
        fields = ['libelle',]

class IngredientModelForm(forms.ModelForm):
    class Meta:
        model = Ingredient
        fields = ['aliment', 'quantite',]

The main purpose is, Menu have several Aliment through Ingredient class. With javascript, I dynamically add Aliment item from one list (list1) to Menu.Ingredient list (list2).

The only way for the user to add item in list2 is by clicking some "add button" of list1.

The problem is ModelChoiceField comes with select that contains all Aliment instance from database.

I would to manage a kind of tuple ('aliment.id', 'aliment.libelle', 'quantite') for each Ingredient that belongs to Menu instance.

One the one hand, I could be able to display in my Ingredient formset template, something like this :

<table>
    {% for item in menu.ingredient %}
        <tr>
            <td>
                <p name="{{ item.aliment.html_name }}" id="{{ fs.aliment.id_for_label }}"
                value="{{ item.aliment.id }}">{{ fs.aliment.libelle }}</p>
            </td>
            <td><input type="number" name="{{ item.quantite.html_name }}" value="{{ item.quantite.value }}" id="{{ item.quantite.id_for_label }}" /></td>
        </tr>
    {% endfor %}
</table>

On the other hand, what is the way for getting Aliment instance of Ingredient formset iteration ?

I don't know if Django Form and Django philosophy are made for this.

Could you make some suggestions please.

Thanks !

1

There are 1 best solutions below

0
On

I realized I was confused when asking question. I will try to be more concise next time.

Anyway, I found a solution to many problem (Maybe you will understand what I meant :p).

I added libelle field to my IngredientModelForm like this :

class IngredientModelForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print(kwargs['instance'])
        self.fields['libelle'].initial = kwargs['instance'].aliment.libelle

    class Meta:
        model = Ingredient
        fields = ['aliment', 'quantite',]

    libelle = forms.CharField(max_length=50)

So I can access to libelle field in my template.
In my view file I did like this :

def add_menu(request):
    ingredientFormSet = modelformset_factory(Ingredient, form=IngredientModelForm, fields=('aliment', 'quantite'), extra=0)
    if request.method == 'POST':
        form = MenuForm(request.POST)
        formset = ingredientFormSet(request.POST)
        if form.is_valid() and formset.is_valid():
            ...
    else:
        menu = Menu.objects.all()[0]
        form = MenuModelForm(instance=menu)
        formset = ingredientFormSet(queryset=menu.ingredient_set.all())

So for each IngredientModelForm instance, its initial value for libelle field will be the value of the Aliment instance attached to Ingredient.

Let's take a look at template :

<form action="" method="POST">
    {{ formset.management_form }}
    <ul>
        {% for fs in formset.forms %}
        <li>
        {{ fs.id.label_from_instance }}
            <span><input type="hidden" name="{{ fs.id.html_name }}" value="{{ fs.id.value }}" id="{{ fs.id.id_for_label }}" /></span>
            <span><input type="hidden" name="{{ fs.aliment.html_name }}" value="{{ fs.aliment.value }}" id="{{ fs.aliment.id_for_label }}" /></span>
            <span>{{ fs.libelle.value }}<input type="hidden" name="{{ fs.libelle.html_name }}" value="{{ fs.libelle.value }}" id="{{ fs.libelle.id_for_label }}" /></span>
            <span><input type="number" name="{{ fs.quantite.html_name }}" value="{{ fs.quantite.value }}" id="{{ fs.quantite.id_for_label }}" /></span>
        </li>
        {% endfor %}
    </ul>
    <input type="submit" value="Ajouter" />
</form>

So, I can display name of Aliment with a select widget (because my Aliment choices are managed with an other list).

I don't know if it's a good practice for Django.

Feel free to suggest an other simple solution ! :)