ManyToManyField

Hey everyone,

I am doing a ToDo app which should look like this:

I have a submit field to add a new list which was never working, not this afternoon and now. The list is below. It is still ‘a work in progress’. Inside of the list I want to have several items linked to the title. The current idea is to create the list with the title and create afterwards the items (perhaps not a good idea…)

This afternoon, I was able to display the title and one list item on the screen from the database but after I realized that I want several list items and not just one, I started to see how to do that. So, I found the ManyToManyField. But I am not able to create anything in the admin panel to display the ToDo List or items on the screen.

models.py:

from django.db import models


class TodoItem(models.Model):
	item = models.CharField(max_length=200)
	completed = models.BooleanField(default=False)

	def __str__(self):
		return self.item
		

class TodoList(models.Model):
	title = models.CharField(max_length=200)
	items = models.ManyToManyField(TodoItem)

	def __str__(self):
		return self.title

forms.py:

from django import forms
from .models import TodoList, TodoItem

class TodoListForm(forms.ModelForm):
	class Meta:
		model = TodoList
		fields = ['title', 'items']


class TodoItemForm(forms.ModelForm):
	class Meta:
		model = TodoItem
		fields = ['item', 'completed']

admin.py:

from django.contrib import admin
from .models import TodoList


class TodoListAdmin(admin.ModelAdmin):
	filter_horizontal = ('todolist',)

admin.site.register(TodoList)

I am not sure about the filter_horzontal because it was difficult to find information in the docs of Django.

views.py:

from django.shortcuts import render, redirect
from django.contrib import messages

from .models import TodoList, TodoItem
from .forms import TodoListForm, TodoItemForm


def home(request):
	if request.method == 'POST':
		form = TodoListForm(request.POST or None)
		if form.is_valid():
			form.save()
			todos = TodoList.objects.all()
			messages.success(request, ('New Todo List added.'))
			return render(request, 'homepage.html', {'todos': todos})
		else:
			todos = TodoList.objects.all()
			return render(request, 'homepage.html', {'todos': todos})

	todos = TodoList.objects.all()
	return render(request, 'homepage.html', {'todos': todos})


def complete(request, todo_id):
	todo = TodoList.objects.get(id=todo_id)
	todo.completed = True
	todo.save()
	return redirect('home')


def incomplete(request, todo_id):
	todo = TodoList.objects.get(id=todo_id)
	todo.completed = False
	todo.save()
	return redirect('home')


def edit(request):
	return render(request, 'edit.html', {})

homepage.html:

{% extends 'index.html' %}

{% block content %}

<!-- Message component -->
{% if messages %}
        {% for message in messages %}
            <article class="message is-success">
                <div class="message-header">
                    <p>Nice!</p>
                </div>
                <div class="message-body">
                    {{ message }}
                </div>
            </article>
        {% endfor %}
    {% endif %}
<!-- End of Message component -->

<!-- Add Todo component -->
	<div class="container my-3">
		<form method="post">
		{% csrf_token %}
			<div class="input-group mb-3">
		  		<input name="title" type="text" class="input" placeholder="Name your new Todo List">
		  		<div class="input-group-append">
		    		<button class="btn btn-primary" type="submit">Submit</button>
		  		</div>
			</div>
		</form>
	</div>
<!-- End of Add Todo component -->


<!-- Card component -->
	
<!-- static values to show the table -->
	<div class="container">
		<div class="row">
    		<div class="col-md-6">    			
				<div class="card border-dark bg-light my-3" >
					<div class="card-header bg-transparent "> ToDoro App </div>
					<div class="card-body">
				    	<div class="card" >

				    	<table class="table table-striped">
  							<tbody>
							    <tr>
							      <td>
							      	
							      		<a href="#"><i class="far fa-check-circle"></i></a>
							      	
							      		<a href="#"> <i class="far fa-circle"></i> </a>
							      	
							      </td>
							      <td> 
							      	Model has to be fixed
							      </td>
							      <td><a href="#"><i class="far fa-edit"></i></a> <a href="#"><i class="far fa-trash-alt"></i></a></td>
							    </tr>

							    <tr>
							      <td>
							      	
							      		<a href="#"><i class="far fa-check-circle"></i></a>
							      	
							      		<a href="#"> <i class="far fa-circle"></i> </a>
							      	
							      </td>
							      <td> 
							      	Admin has to be fixed
							      </td>
							      <td><a href="#"><i class="far fa-edit"></i></a> <a href="#"><i class="far fa-trash-alt"></i></a></td>
							    </tr>
							</tbody>
						</table>
						  
						</div>
						<div class="mt-2">
				    		<a href="#" class="btn btn-danger"> Delete List</a>
						</div>
				    	
				  	</div>
				</div>
			</div>
		</div>


	</div>
<!-- End of static values -->

	<div class="container">
		{% for todo in todos %}
  		<div class="row">
    		<div class="col-md-6">    			
				<div class="card border-dark bg-light my-3" >
					<div class="card-header bg-transparent "> {{ todo.title }} </div>
					<div class="card-body">
				    	<div class="card" >

				    	<table class="table table-striped">
  							<tbody>
							    <tr>
							      <td>
							      	{% if todo.completed == True %}
							      		<a href="{% url 'complete' todo.id %}"><i class="far fa-check-circle"></i></a>
							      	{% else %}
							      		<a href="{% url 'incomplete' todo.id %}"> <i class="far fa-circle"></i> </a>
							      	{% endif %}
							      </td>
							      <td> 
							      	{% if todo.completed == True %}
							    		<span style="text-decoration: line-through;"> {{ todo.items }} </span>
							    	{% else %}
							    		{{ todo.items }}
							    	{% endif %}
							      </td>
							      <td><a href="{% url 'edit' %}"><i class="far fa-edit"></i></a> <a href="{% url 'edit' %}"><i class="far fa-trash-alt"></i></a></td>
							    </tr>
							</tbody>
						</table>
						  
						</div>
						<div class="mt-2">
				    		<a href="#" class="btn btn-danger"> Delete List</a>
						</div>
				    	
				  	</div>
				</div>
			</div>
		</div>
		{% endfor %}
	</div>
	

<!-- End of card component -->

{% endblock %}

To be able to display something on the homepage on localhost I used static values.

Admin panel:

Here on the Admin panel I am not able to enter an item that is why I added the filter_horzontal inside of the admin.py file but I can not make it work or because there are no ToDo lists available I can not add any? I have no clue, I am guessing.

While reading again the post I realized that I did a mistake in admin.py:

from django.contrib import admin
from .models import TodoList


class TodoListAdmin(admin.ModelAdmin):
	filter_horizontal = ('todolist',)

admin.site.register(TodoListAdmin) # is has to be TodoListAdmin

but I got this error in my terminal:

  File "/Users/mac/Desktop/Django/ToDoro/ToDoro/ToDoro_app/admin.py", line 8, in <module>
    admin.site.register(TodoListAdmin)
  File "/Users/mac/Desktop/Django/ToDoro/venv/lib/python3.9/site-packages/django/contrib/admin/sites.py", line 117, in register
    for model in model_or_iterable:
TypeError: 'MediaDefiningClass' object is not iterable

So, I checked again and I changed admin.py:

from django.contrib import admin
from .models import TodoList


class TodoListAdmin(admin.ModelAdmin):
	filter_horizontal = ('todolist',)

admin.site.register(TodoList, TodoListAdmin)

stop the terminal and restart the terminal and get error:

raise SystemCheckError(msg)
django.core.management.base.SystemCheckError: SystemCheckError: System check identified some issues:

ERRORS:
<class 'ToDoro_app.admin.TodoListAdmin'>: (admin.E019) The value of 'filter_horizontal[0]' refers to 'todolist', which is not a field of 'ToDoro_app.TodoList'.

System check identified 1 issue (0 silenced).

I can not find the Django docs where I found the example:

class NameAdmin(admin.ModelAdmin):
	filter_horizontal = ('category',)

EDIT:
admin.py:

from django.contrib import admin
from .models import TodoList


class TodoListAdmin(admin.ModelAdmin):
	filter_horizontal = ('items',)

admin.site.register(TodoList, TodoListAdmin)

now the admin panel display is different but I can not add something:

How can I enter an item?

Sorry for this long text.

Thanks
Doro

Create a ModelAdmin for TodoItem and add your entries there. You can then assign those Items to a List from the List.

(I thought there was a widget that would appear on those related entries allowing you to create them at that point, but I’m not seeing right offhand where that’s defined. You can try using an InlineModelAdmin object)