create Todo List and Item

Hey everyone,

I have two issues in my app.
It is a Todo application, on the base.html page there is the form to create the first Todo title and one item. This form is not working or I do not know. I can not find the issue.
And the second issue is that I can not display the items which I created with the Admin panel on the homepage.

base.html:

<div class="container">
        <form method="POST">
            {% csrf_token %}
            <div class="row">
                <div class="col-md-6">
                    <div class="card border-dark bg-light my-3" >
                        <div class="card-header bg-transparent ">
                            <div class="input-group">
                                <input
                                    name="title"
                                    type="text" 
                                    class="form-control" 
                                    placeholder="ToDoro Create new List">
                            </div>
                        </div>

                        <div class="card-body">
                            <div class="card" >
                            <table class="table table-striped">
                                <tbody>
                                    <tr>
                                        <td>
                                            <i class="far fa-circle"></i>
                                        </td>
                                        <td>
                                            <div class="input-group">
                                                <input 
                                                    name="items"
                                                    type="text" 
                                                    class="form-control"
                                                    placeholder="Add an Item">
                                            </div>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                              
                            </div>
                            <div class="mt-2">
                                <button 
                                    type="submit" 
                                    class="btn btn-primary">
                                    Create List
                                </button>
                            </div>
                            
                        </div>
                    </div>
                </div>
            </div>
        </form>
    </div>

homepage.html:

<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>
						      			 <i class="far fa-circle"></i>
							      	</td>
							      	{% for item in todo_items %}
							     	<td>
							     		{{ item.items }}
							     	</td>
							     	{% endfor %}
							     	<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>

models.py:

from django.db import models

class TodoItem(models.Model):
	item = models.CharField(max_length=200)

	def __str__(self):
		return self.item
		

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


	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', 'completed']


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

views.py:

from django.shortcuts import render, redirect
from django.contrib import messages
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.base import TemplateView

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()
			todo_items = TodoItem.objects.all()
			messages.success(request, ('New Todo List added.'))
			return render(request, 'homepage.html', {'todos': todos}, {'todo_items': todo_items})
		else:
			todos = TodoList.objects.all()
			return render(request, 'homepage.html', {'todos': todos})

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


class TodoListUpdate(UpdateView):
	model = TodoList
	fields = '__all__'
	tempate_name_suffix = '_update_form'


class TodoListCreateView(CreateView):
	model = TodoList
	fields = '__all__'


class TodoListUpdateView(TemplateView):
	template_name = '_update_form.html'

	def get_context_data(self, **kwargs):
		context = super().get_context_data(**kwargs)
		todo_list = get_object_or_404(TodoList, pk=self.kwargs['pk'])
		context['todo_list'] = todo_list
		context['todo_list_form'] = TodoListForm(instance=todo_list)
		context['todo_item_form'] = TodoItemForm(instance=todos.todoItem)

		return context

I tried already some thinks but I can not make it work. I am lost.

I would appreciate some hints that I can make it work and learn.

Thank you
Doro

You are not rendering the form, you’re rendering html fields directly.

Review the docs at Working with forms | Django documentation | Django

Thank you Ken so much. It is so great to get answers to questions. Your answers are helpful. I do not say that all the time but Thank you!

The link you wrote was of course a good one. And it helped. But I still can not accomplish what I want. I think the model is wrong. I am imagine a todo list like a Trello list. I want to have one list with one title and several items on the list.

my models.py:

from django.db import models

class TodoItem(models.Model):
	item = models.CharField(max_length=200)

	def __str__(self):
		return self.item
		

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


	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', 'completed']


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

And to visualize it on the website, what I want to achieve:

Can you please help me? Or is that not possible? I have searched some todo apps with Django just to get some input but I could not find a todo app like mine, so perhaps it is not possible.

Would be great.

Thank you
Doro

What you’re looking for in this case would be an InlineFormset

The TodoList would be the form, the TodoItem would be the formset entries.

(However, I’m not sure you really want a ManyToMany relationship between the TodoList and the TodoItem. The traditional relationship is a ForeignKey in TodoItem referring to TodoList.

Thank you Ken,

With the InlineFormset I am not sure but I have an other question first for the ForeignKey Field.

You wrote that the

The traditional relationship is a ForeignKey in TodoItem referring to TodoList.

you mean like this?

class TodoItems(models.Model):
	items = models.CharField(max_length=200)

	def __str__(self):
		return self.item
		

class TodoList(models.Model):
	title = models.CharField(max_length=200)
	item = models.ForeignKey('TodoItem', on_delete=models.CASCADE, blank=True, null=True)
	completed = models.BooleanField(default=False)

or the other way around? I would say I need “many” TodoItems to “one” TodoList. I am not sure because I checked the 4.1 and the 4.0 docs of Django. And I will use the 4.0 as a reference:

from django.db import models

class Reporter(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    email = models.EmailField()

    def __str__(self):
        return "%s %s" % (self.first_name, self.last_name)

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)

    def __str__(self):
        return self.headline

    class Meta:
        ordering = ['headline']

Which one is the “one” and which one is “many”? Are there many articles referring to one reporter? Or one article referring to many reporters? That is not clear for the docs. For me is both possible.

Doro

Yes, it’s the other way around. You have many TodoItem related to a single TodoList.

In a ManyToOne relationship, the “Many” side is always the table with the ForeignKey.

Thanks again Ken.

With the help of a YouTube video to understand the InlineFormset I am able to display the data on the page.

views.py:

from django.shortcuts import render, redirect
from .models import TodoItem, TodoList

from django.forms import inlineformset_factory


def index(request, todolist_id):
	todolist = TodoList.objects.get(pk=todolist_id)
	# the inlineformset needs two models(parent model, child model) and enter the fields you want to use from the child model
	TodoListFormset = inlineformset_factory(TodoList, TodoItem, fields=('item',), extra=1)

	if request.method=='POST':
		# the instance is associated with the parent model
		formset = TodoListFormset(request.POST, instance=todolist)		
		if formset.is_valid():
			formset.save()
			
			return redirect('index', todolist_id=todolist.id)

	formset = TodoListFormset(instance=todolist)

	return render(request, 'base.html', {'formset': formset, 'todolist': todolist})

I would like to know if it is possible to get all TodoLists on one page? So, the first TodoList then the second TodoList and the third TodoList etc. on one page under each other. I understand that the TodoList is a queryset and to access one of them I need the pk.

I tried to get all objects and display them on the page:
views.py:

from django.shortcuts import render, redirect
from .models import TodoItem, TodoList

from django.forms import inlineformset_factory


def index(request):
	todolist = TodoList.objects.all()
	# the inlineformset needs two models(parent model, child model) and enter the fields you want to use from the child model
	TodoListFormset = inlineformset_factory(TodoList, TodoItem, fields=('item',), extra=1)

	if request.method=='POST':
		# the instance is associated with the parent model
		formset = TodoListFormset(request.POST, instance=todolist)		
		if formset.is_valid():
			formset.save()
			
			return redirect('index')

	formset = TodoListFormset(instance=todolist)

	return render(request, 'base.html', {'formset': formset, 'todolist': todolist})

but it ends with the error:
'QuerySet' object has no attribute 'pk'

I hope I could make it understandable what I mean.

Thank you
Doro

You’re going to end up with nested loops.

Your outer loop is going to iterate over your todolist queryset.

(Note, for clarity, I suggest you change your query to be:
todolist_list = TodoList.objects.all()
to make it clear that todolist_list is a list of TodoList objects.)

Then, for each instance of todolist in todolist_list, you will create an instance of the formset corresponding to that instance of todolist.

Since you’re creating multiple instances of a formset, you’ll want to ensure you use the prefix attribute on each formset to make them distinct in the rendered HTML.

You’ll need to repeat the process for both the GET and POST portions of your view so that on submittal, each submission is handled by the “right” formset.

Your template will also need to be changed to iterate over todolist_list to render all the different formsets.

1 Like