Rahul Sharma
Rahul Sharma

Reputation: 2495

Edit a formset in django

In my django app I am trying to understand many to many relationship and I am using formset to store the data like this:

Views.py

def Team_Form(request):

   if request.POST:
        form = TeamForm(request.POST)
        form.player_instances = PlayerFormset(request.POST)
        if form.is_valid():
            team= Team()
            team.tname= form.cleaned_data['tname']
            team.save()

        if form.player_instances.cleaned_data is not None:
            for item in form.player_instances.cleaned_data:
                player = Player()
                player.pname= item['pname']
                player.hscore= item['hscore']
                player.age= item['age']
                player.save()
                team.player.add(player)
            team.save()

   else:
        form = TeamForm()
        return render(request, 'packsapp/employee/new.html', {'form':form})

Models.py

class Player(models.Model):
    pname = models.CharField(max_length=50)
    hscore = models.IntegerField()
    age = models.IntegerField()
    def __str__(self):
       return self.pname

class Team(models.Model):
    tname = models.CharField(max_length=100)
    player= models.ManyToManyField(Player)
    def __str__(self):
        return self.tname

Forms.py

class PlayerForm(forms.Form):
    pname = forms.CharField()
    hscore= forms.IntegerField()
    age = forms.IntegerField()

PlayerFormset= formset_factory(PlayerForm)

class TeamForm(forms.Form):
   tname= forms.CharField()
   player= PlayerFormset()

HTML

<html>
<head>

    <title>gffdfdf</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="/static/jquery.formset.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

</head>
<body>

<div class="container">

    <form id="myForm" action="" method="post" class="">
        {% csrf_token %}
        <h2> Team</h2>
        {% for field in form %}
        {{ field.errors }}
        {{ field.label_tag }}  {{ field }}
        {% endfor %}
        {{ form.player.management_form }}

        <h3> Product Instance(s)</h3>
        <table id="table-product" class="table">
            <thead>
            <tr>
                <th>player name</th>
                <th>highest score</th>
                <th>age</th>
            </tr>

            </thead>
            {% for player in form.player %}
            <tbody class="player-instances">

            <tr>
                <td>{{ player.pname }}</td>
                <td>{{ player.hscore }}</td>
                <td>{{ player.age }}</td>
                <td><input id="input_add" type="button" name="add" value=" Add More "
                           class="tr_clone_add btn data_input"></td>

            </tr>

            </tbody>
            {% endfor %}
        </table>
        <button type="submit" class="btn btn-primary">save</button>

    </form>
</div>

<script>
    var i = 1;
    $("#input_add").click(function () {
        $("tbody tr:first").clone().find(".data_input").each(function () {
            if ($(this).attr('class') == 'tr_clone_add btn data_input') {
                $(this).attr({
                    'id': function (_, id) {
                        return "remove_button"
                    },
                    'name': function (_, name) {
                        return "name_remove" + i
                    },
                    'value': 'Remove'
                }).on("click", function () {
                    var a = $(this).parent();
                    var b = a.parent();
                    i = i - 1
                    $('#id_form-TOTAL_FORMS').val(i);
                    b.remove();

                    $('.player-instances tr').each(function (index, value) {
                        $(this).find('.data_input').each(function () {
                            $(this).attr({
                                'id': function (_, id) {
                                    console.log("id", id)
                                    var idData = id;
                                    var splitV = String(idData).split('-');
                                    var fData = splitV[0];
                                    var tData = splitV[2];
                                    return fData + "-" + index + "-" + tData
                                },
                                'name': function (_, name) {
                                    console.log("name", name)
                                    var nameData = name;
                                    var splitV = String(nameData).split('-');
                                    var fData = splitV[0];
                                    var tData = splitV[2];
                                    return fData + "-" + index + "-" + tData
                                }
                            });
                        })
                    })
                })
            } else {
                $(this).attr({
                    'id': function (_, id) {
                        console.log("id", id)

                        var idData = id;
                        var splitV = String(idData).split('-');
                        var fData = splitV[0];
                        var tData = splitV[2];
                        return fData + "-" + i + "-" + tData
                    },
                    'name': function (_, name) {
                        console.log("name", name)

                        var nameData = name;
                        var splitV = String(nameData).split('-');
                        var fData = splitV[0];
                        var tData = splitV[2];
                        return fData + "-" + i + "-" + tData
                    }
                });

            }
        }).end().appendTo("tbody");
        $('#id_form-TOTAL_FORMS').val(1 + i);
        $("tbody tr:last :input").each(function () {
            $(this).attr({
                'id': function (_, id) {
                    return id.replace(/\d/g, i)
                },
                'name': function (_, name) {
                    return name.replace(/\d/g, i)
                },
            })
        })

        i++;

    });
</script>

</body>
</html>

What I failed to understand is that how to edit the formset that I just Saved or to better phrase the question, How to pass the saved instance to the formset to edit it ?

Update:

I tried the modelformset_factory and it fetches all the objects from Player in post as well as update

Forms.py

PlayerFormset= modelformset_factory(Player, fields=('pname','hscore','age'))

Screenshot:

While tried to edit team Matt:

enter image description here

Upvotes: 0

Views: 1842

Answers (3)

bb4L
bb4L

Reputation: 919

Additionally to the answer https://stackoverflow.com/a/61185348/13168118 of Matthieu-OD

you could change the

PlayerFormset= formset_factory(PlayerForm)

to a

modelformset_factory

https://docs.djangoproject.com/en/3.0/ref/forms/models/#django.forms.models.modelformset_factory

and in the init method of the 'TeamForm' you should be able to adjust the queryset of the modelformset to only show the players of this team

if you don't adjust it every player will be shown

EDIT:

i would also suggest that you use modelforms since your forms are for models: https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#modelform

i also found this question which seems to be similar: Django ModelForm for Many-to-Many fields

Upvotes: 0

Paul Rene
Paul Rene

Reputation: 710

If you want that, then you have to pass the filled-in form back to the template. A good example of this in the docs is https://docs.djangoproject.com/en/3.0/topics/forms/formsets/#using-a-formset-in-views-and-templates. In your code this would look like this (haven't tried in with a template):

def team_view(request):
    PlayerFormset = formset_factory(PlayerForm)

    if request.POST:
        form = TeamForm(request.POST)
        form.player_instances = PlayerFormset(request.POST)
        if form.is_valid():
            team = Team()
            team.tname = form.cleaned_data['tname']
            team.save()

            if form.player_instances.cleaned_data is not None:
                for item in form.player_instances.cleaned_data:
                    player = Player()
                    player.pname= item['pname']
                    player.hscore= item['hscore']
                    player.age= item['age']
                    player.save()
                    team.player.add(player)
                team.save()

        else:
            form = TeamForm()
        return render(request, 'packsapp/employee/new.html', {'form': form})

I have changed a couple of things. First of all, use lowercase for function based views, and try not to use the name 'form' in a view. Furthermore, notice the indentation: the 'if form.player_instances.cleaned_data...' has an extra indent. There is not much use checking players if there is no team, you will not be able to save the (non-existent) team. Then: the 'return'-statement is now on the same level as the first if/else statement. In your version there is no return after saving the form. By doing this, the filled-in form (from the if-part of the statement) is returned in the context-variable. It is then the job of the template to decide what to do with it. In the else-case, an empty form is returned.

I've noticed that this project is apparently a tutorial, there are at least two related questions on StackOverflow: Django Dynamic form for manytomany relationship and How can i save django dynamic formset data using forms and views. Maybe you can learn from those.

Upvotes: 0

Matthieu-OD
Matthieu-OD

Reputation: 196

Many to many here means that one player can be in several team and also that one team can have many players.

To resolve your problem you have to create an other view, (link to the same form) that will display your form but already fill. In your function pk for your team.

def updateTeam(request,pk):

  team = Team.objects.get(id=pk)
  form = TeamForm(instance=team)


if request.method == "POST":
    form = TeamForm(request.POST, instance=team)
    if form.is_valid():
        form.save()

context = {'form': form}
return render(request, 'accounts/order_form.html', context)

That should resolve your problem !

Do not hesitate if you have any questions

Upvotes: 1

Related Questions