John Rogerson
John Rogerson

Reputation: 1183

Django Model With ManyToMany Not Saving

I have a list of menus--with items listed on the menu.

models.py:

class Menu(models.Model):
    season = models.CharField(max_length=20)
    items = models.ManyToManyField('Item', related_name='items')
    created_date = models.DateTimeField(default=timezone.now)
    expiration_date = models.DateTimeField(blank=True, null=True)

    def __str__(self):
        return self.season

class Item(models.Model):

    name = models.CharField(max_length=200)
    description = models.TextField()
    chef = models.ForeignKey('auth.User')
    created_date = models.DateTimeField(
            default=timezone.now)
    standard = models.BooleanField(default=False)
    ingredients = models.ManyToManyField(
        'Ingredient', related_name='ingredients'
    )

    def __str__(self):
        return self.name

class Ingredient(models.Model):
    name = models.CharField(max_length=200)

    def __str__(self):
        return self.name

For some reason--in the admin--i can edit the menu and it will save the items properly--but if i do so in the edit view for the menu it does not save the items (it does however save the season and expiration_date). That's why i thought maybe it was the ManyToMany relationship? Any ideas?

views.py:

def edit_menu(request, pk):
    menu = get_object_or_404(Menu, pk=pk)
    items = Item.objects.all()
    admin = User.objects.get(username="admin")
    if request.method == "POST":
        ingredient = Ingredient(name="bug")
        ingredient.save()
        items = []

        for i in request.POST.getlist('items'):
            item = Item(name=i, chef=admin, description = "your description here", standard=False)
            item.save()
            items.append(item)
            item.ingredients.add(ingredient)

        if not Menu.objects.filter(pk=pk).exists():
            menu=Menu(season=request.POST.get('season', ''),
                      expiration_date=datetime.strptime(request.POST.get('expiration_date', ''), '%m/%d/%Y'))

        else:
            menu=Menu.objects.get(pk=pk)
            menu.items.delete()

        menu.save()

        for item in items:
            menu.items.add(item)
            return redirect('menu_detail', pk=menu.pk)

    return render(request, 'menu/change_menu.html', {
    'menu': menu,
    'items': items,
})

and menu detail page,

template

{% extends "layout.html" %}
{% block content %}
        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                    <div class="post">
                        <h1>
                            {% if user.is_authenticated %}
                                <a class="btn btn-default" href="{% url 'menu_edit' pk=menu.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
                            {% endif %}
                            {{ menu.season }}
                        </h1>
                        <h2>On the menu this season:</h2>
                        <ul>
                            {% for item in menu.items.all %}
                                <li><a href="{% url 'item_detail' pk=item.pk %}">{{ item }}</a></li>
                            {% endfor %}
                        </ul>
                        {% if menu.expiration_date %}
                            <div class="date">
                                Menu expires on {{ menu.expiration_date|date:"F j, Y" }}
                            </div>
                        {% endif %}
                    </div>
                </div>
            </div>
        </div>
{% endblock %}

UPDATE --changed edit view to this... now getting many to many error

def edit_menu(request, pk):
    menu = get_object_or_404(Menu, pk=pk)
    items = Item.objects.all()
    admin = User.objects.get(username="admin")
    if request.method == "POST":
        ingredient = Ingredient(name="bug")
        ingredient.save()
        item = Item(name=request.POST.get('items', ''), chef=admin, description = "your description here", standard=False)
        item.save()
        item.ingredients.add(ingredient)
        menu = Menu(season=request.POST.get('season', ''),
                    expiration_date=datetime.strptime(request.POST.get('expiration_date', ''), '%m/%d/%Y'))
        menu.save()
        menu.items.add(item)
        return redirect('menu_detail', pk=menu.pk)

    return render(request, 'menu/change_menu.html', {
    'menu': menu,
    'items': items,
})

UPDATE --adding change_menu template below

{% extends "layout.html" %}
{% block content %}
        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                    <h1>Change menu</h1>
                    <form action="" method="POST">{% csrf_token %}
                        <label for="season">Season:</label>
                        <input type="text" name="season" value="{{ menu.season }}">
                        <br />
                        <label for="items">Items:</label>
                        <select multiple>
                        {% for item in items %}
                            <option value="{{ item }}">{{ item}}</option>
                        {% endfor %}
                        </select>
                        <br />
                        <label for="expiration_date">Expiration Date:</label>
                         <input type="text" name="expiration_date" value="{{ menu.expiration_date|date:"m/d/Y" }}">
                        <br />
                        <input type="submit" value="Submit" /> 
                    </form>
                </div>
            </div>
        </div>
{% endblock %}

adding new_menu template below

{% extends "layout.html" %}
{% block content %}

   <div class="content container">
            <div class="row">
                <div class="col-md-8">
                    <h1>Create New Menu</h1>
                    <form method="POST" action="">
                        {% csrf_token %}
                        {{ form.as_p }}
                        <input type="submit" class="button" value="Save">
                    </form>
                </div>
                </div>
       </div>


{% endblock %}

Upvotes: 2

Views: 3253

Answers (2)

Matt Cremeens
Matt Cremeens

Reputation: 5151

First create and save your ManyToMany objects and then add them AFTER they have already been saved

ingredient = Ingredient(add parameters here)
ingredient.save()
# put items in a list so you can add them to menu later
items = []
# make new items from option box in template
for i in request.POST.getlist('items'):
    item = Item(name=i, chef=admin, description = "your description here", standard=False)
    item.save()
    items.append(item)
item.ingredients.add(ingredient)

if not Menu.objects.filter(pk=pk).exists():
    menu=Menu(season=request.POST.get('season', ''), expiration_date=datetime.strptime(request.POST.get('expiration_date', ''), '%m/%d/%Y'))
else:
    menu=Menu.objects.get(pk=pk)
    # we will be replacing the old menu items
    menu.items.clear()
menu.save()
for item in items:
    menu.items.add(item)

Upvotes: 1

zaidfazil
zaidfazil

Reputation: 9235

You need to edit your views like this,

def edit_menu(request, pk):
    menu = get_object_or_404(Menu, pk=pk)
    items = Item.objects.all()
    if request.method == "POST":
        item_name = request.POST.get('items')
        season = request.POST.get('season')
        exp_date = datetime.strptime(request.POST.get('expiration_date'), '%m/%d/%Y')
        try:
            item = Item.objects.get(name=item_name)
        except:
            item = Item.objects.create(name=item_name, description = "description_here", ingredients="ingredients_here", standard=False)
        menu = Menu.objects.create(season=season, expiration_date=exp_date)
        menu.items.add(item)
        menu.save()
        return redirect('menu_detail', pk=menu.pk)

    return render(request, 'menu/change_menu.html', {
    'menu': menu,
    'items': items,
})

You can't add a many to many relation, without commiting the model to the database. First you have to save the menu object, then you need to add item to the items of menu.

Upvotes: 1

Related Questions