Create a Todo application using Django. Part 1

Hello again. In anticipation of the start of the Python Web Developer course , our freelance writer has prepared some interesting material that we are happy to share with you.




Django is a powerful web application framework. Initially, Django was created in order to quickly create, for example, news sites (or other sites that need to be created as quickly as possible). And after native PHP, the feeling that you are riding a very fast development machine does not leave. To see all its features for rapid development, we will try to create a simple Todo application.



Let's start with the wording of the short t.z. We will have a small web application with layout on Bulma (yes, I really like Bulma. Maybe someday I will turn off on Bootstrap or Picnic, but everything has its time). We (so far) do not have authorizations and the user can create, edit and delete either the to-do category or the todo card, which is associated with any category that the user created. A Todo card or category can be deleted by checking the checkbox and clicking the delete button.

Django Core Concepts


Let's talk a little about Django. Django implements the MVT (Model View Template) architectural pattern, which is slightly different from the familiar MVC (Model View Controller) that runs Ruby on Rails and Laravel.

Model The model in Django describes the data scheme in the database. With Django ORM, you can independently describe fields and any other data types, and make migrations to simplify development.

View In the view in Django, you set the basic logic and algorithms of the application, get various data from the database or manipulate it. The view is usually based on the request \ response functions. Response is usually HTTP redirect, HTTP error (404), MimeTypes or some kind of pattern.

TemplateTemplate in Django is a simple HTML code with a special template language Django. DTL (Django Template Language) is a language with which you can dynamically change the content of a page (for example, change the username on a page, depending on the name of the authorized user).

Settings The settings file in Django, which contains all the settings for your web application. It includes a secret key, templates folders, middlewares (which are responsible, for example, to prevent other users from seeing your private albums), a database connection, and much more.

Url The routing configuration file is about the same as in Angular or Laravel. This associates the view with url requests.

Admin Page Since Django was originally designed for rapid prototyping and deployment of news sites, the admin panel is included by default.

Install Python and Django


Creative retreat
( ) . Python . , . , .

Python versions


Until recently, two main Python branches were actively supported and developed: 2.7 and 3.x. I will use version 3.7.3 in this article, but in fact it is not so important. If you really want to know the difference between the two, there is a special wiki . On the other hand, now it makes no sense to use Python version 2.7 - the language update stopped at 2.7.17 (if I understand the documentation on the official site correctly). This means that it makes sense to transfer projects written in Python 2.7.x to a new branch, but writing new ones in 2 versions is completely pointless.

Python installation


If you are working on Mac or Ubuntu, you probably already have Python installed, but there are 2 versions. Python of the third version will have to be downloaded separately, and you can call it on the command line through python3. In any case, it’s best to download the latest release here .

Create your own virtual environment


In fact, you can start developing the first application on Django without creating your own virtual environment, but the skill of creating a virtual environment can come in handy if, for example, you develop an application with a specific version of the library and do not want to install libraries globally and litter your system.

So how do you use virtual env?



1) The easiest option. You can download the wonderful IDE from JET BRAINS PyCharm Community Edition from here . After installing PyCharm, create a new project, and Pycharm will offer you to create Virtual Env by default, in which it will be possible to install the required version of Django (or by default the latest one, which at the time of writing of this article 3.0.2):

pip3 install django

2) A slightly more hardcore option:

What if you want to run Django in virtual env, for example, in your favorite folder?
First, create a folder in which we will create:

	mkdir myfirstdjango && cd myfirstdjango

Next, enter the following commands to activate venv, where django_env is the name of our virtual environment:

	python3 -m venv django_env
	source django_env/bin/activate

Further, our virtual environment was activated. We can supply the necessary packages. In our case, this is Django:

	pip3 install django

If you want to turn off the virtual environment in order to return to your global python (return to the system context), enter the following command:

    deactivate

Well, I hope we figured out the virtual environment. If you have any problems and additional questions, useful links can be found here and here .

Creating the project itself


Suppose you choose one of the ways to create your own virtual environment (or even do everything globally, well, no one forbids you to do this). Now we go to the project folder and begin its creation:

    django-admin startproject todo  #    
    cd todo #  
    python manage.py startapp todolist  #   todolist
	python3 manage.py runserver 8100 #      , 8000  
 

So, after Django opened the start page, you need to install our todolist application in the main application. Open settings.pyand add to our existing list of applications our own todolist:

INSTALLED_APPS = [
	# ,     ,     
    'todolist',
]

The next step is to link the application with the database. If databases are not what you want to mess with, you should use the default solution, SQlite. I decided to use PostgreSQL - it is popular and classically related to Django, in addition, then we may want to increase the performance of the application. There are a lot of instructions on how to install PostgreSQL on all operating systems. I am developing for MacOS and with some small dances with a tambourine, I installed this database by downloading Postgres.app from here . As for the interfaces for the database, here I used Posticoand a trial version for developing the application is quite enough for us (although, in principle, you can do without it, because all our interaction with the database will be built through the web application itself and the migration). In addition, I had to put psycopg2 in the virtual environment of the project (without this driver, your application will not work with the database).

Next, you need to configure the work of statics. We are still editing the file settings.py, now at the very end we add work with statics:

STATIC_URL = '/static/'
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static')
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'

In order for your statics to work, check that the package responsible for the statics was in the INSTALLED_APPS list :

django.contrib.staticfiles,in case an error occurs.
And the last thing in the preparatory work, we still need to configure the basic work of url in the project:

	from django.conf.urls import url
	from django.contrib import admin
	from todolist.views import todo
	from todolist.views import category
	from todolist.views import redirect_view
 
urlpatterns = [
	url(r'$^', redirect_view ),
	url(r'^admin/', admin.site.urls),
	url(r'^todo/', todo, name="TodoList"),
	url(r'^category/', category, name="Category"),
]

I added a redirect because I want localhost to go directly to the category subpage from the default page (so God forbid the user is not lost). We also have two-page routing: categories and cases.

So, I hope your application does not crash. Next, we can finally move on to creating the application itself:

Creating a Todo Model and Categories


Next, we will begin to create a model that will fundamentally interact with our database. To create a model, open the file models.pyin our todolist and start creating. Let's start by creating a category table:

from django.utils import timezone #     todo
from django.db import models
 
class Category(models.Model): #     models.Model
    name = models.CharField(max_length=100) #varchar.    
	class Meta:
    	verbose_name = ("Category") #   
    	verbose_name_plural = ("Categories")  #    
    def __str__(self):
        return self.name  # __str__      

Fine! Yes, here we will have only two columns in the Categories table: id and name. Next, create a table for our affairs. I think everything is clear from the comments:

class TodoList(models.Model):
	title = models.CharField(max_length=250)
	content = models.TextField(blank=True) # 
	created = models.DateField(default=timezone.now().strftime("%Y-%m-%d")) #  
	due_date = models.DateField(default=timezone.now().strftime("%Y-%m-%d")) #      
	category = models.ForeignKey(Category, default="general",on_delete=models.PROTECT) # foreignkey          
	class Meta: #       
        ordering = ["-created"] #     
	def __str__(self):
    	return self.title

After your model is ready, you need to create migrations:

	python3 manage.py makemigrations

And then run the migrations themselves:

	python3 manage.py migrate

Create view


Open the file view.pyin todolist and edit it. First, add the necessary imports and redirect from the main to category:

from django.shortcuts import render, redirect #      
from django.http import HttpResponse
from .models import TodoList, Category #   
 
def redirect_view(request):
	return redirect("/category") #     

Then we begin the creation of our business. The instance of the case will have fields of the text itself, the date by which the case should be completed, the category of the case, and the combined content:

def todo(request):
	todos = TodoList.objects.all() #   todo   
    categories = Category.objects.all() #    

After that, add the functions of adding and deleting cases:

if request.method == "POST": #     POST
	if "Add" in request.POST: #   todo
    	title = request.POST["description"] # 
    	date = str(request.POST["date"]) #,      
        category = request.POST["category_select"] #,      .
        content = title + " -- " + date + " " + category #   
    	Todo = TodoList(title=title, content=content, due_date=date, category=Category.objects.get(name=category))
    	Todo.save() #   
        return redirect("/todo") #   (        )
    if "Delete" in request.POST: #     
        checkedlist = request.POST.getlist('checkedbox') #    ,    
        for i in range(len(checkedlist)): # -    
            todo = TodoList.objects.filter(id=int(checkedlist[i]))
        	todo.delete() # 
return render(request, "todo.html", {"todos": todos, "categories": categories})

That's all with toes. Then we can go to the Categories page. We create a function of categories in which we will also have the function of adding and removing a category. Fundamentally, there will be nothing new here, we will also have the ability to add and remove:

def category(request):
	categories = Category.objects.all()  #   
	if request.method == "POST": #    POST
    	if "Add" in request.POST: #  
        	name = request.POST["name"] #  
        	category = Category(name=name) #     
            category.save() #   
        	return redirect("/category")
    	if "Delete" in request.POST: #    
            check = request.POST.getlist('check') #       todo,       
            for i in range(len(check)):
            	try:
                	ateg = Category.objects.filter(id=int(check[i]))
                	ateg.delete()   # 
            	except BaseException: #        ,       
                	return HttpResponse('<h1>     )</h1>')
	return render(request, "category.html", {"categories": categories})

This is where we end with the file viewand can move on to the templates:

Work with templates


As you remember, in order not to write css again, I used bulma.cssto simplify the layout. Because our category pages and todo will be very similar, I created three files:
base.htmlwhich will include all the same that we have on the pages, and in category.html, the todo.htmldifferences will be located:



Create base.htmland edit it:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>   </title>
  {% load static %}
	<link rel="shortcut icon" type="image/png" href="{% static 'favicon.png' %}"/>
   <link rel="stylesheet" type="text/css" href="{% static 'bulma.min.css' %}">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div django-app="TaskManager">
	<nav class ="navbar is-success" role="navigation" aria-label="main navigation">
     	<div class="navbar-menu ">
        	<div class="navbar-start">
        	<a class="navbar-item" href="../category"> </a>
        	<a class="navbar-item" href="../todo">   </a>
        	</div>
         </div>
	</nav>
<!--               -->
{% block content %}
{% endblock %}
</div>
</body>
</html>

Next, we will go to the page todo.htmland category.html:

Tudushka:

{% extends 'base.html' %}
{% block content %}
<div class="columns has-background-black-ter is-centered has-text-white-bis" style="min-height:101vh;">
	<!--        hero,     inline-css -->
	<div class="column is-half">
    	<h1 class="is-size-3 has-text-centered">   </h1>
    	<form action="" method="post">
        	{% csrf_token %}
        	<!-- csrf      -->
            <div class="field has-text-centered">
            	<label for="description" class="label has-text-white-bis"> </label>
            	<div class="control">
             	   <input type="text" id="description" class="input" placeholder="  ?"
                    	name="description" required>
            	</div>
        	</div>
        	<div class="columns">
            	<div class="column">
                	<label for="category"></label>
                	<div class="control">
                    	<div class="select">
                        	<select id="category" class="select" name="category_select" required>
                            	<!--  required,       .    -->
                            	<option class="disabled" value="">  </option>
                    	        {% for category in categories %}
 	                           <option class="" value="{{ category.name }}" name="{{ category.name }}">
                                    {{ category.name }}</option>
                            	{% endfor %}
                        	</select>
    	                </div>
                	</div>
            	</div>
            	<div class="column">
                	<label for="dueDate"> </label>
                	<input type="date" id="dueDate" class="input calendar" name="date" required>
            	</div>
        	</div>
        	<div class="column">
            	<div class="field">
                	<button class="button is-primary" name="Add" type="submit">
                    	<span class="icon is-small">
                        	<i class="fa fa-plus"></i>
                    	</span>
                    	<span> </span>
                	</button>
             	   <button class="button is-link" name="Delete" formnovalidate="" type="submit">
                    	<span class="icon is-small">
                        	<i class="fa fa-trash-o"></i>
                    	</span>
                    	<span>
                        	 
                    	</span>
                	</button>
            	</div>
        	</div>
        	<div class="list is-hoverable">
            	{% for todo in todos %}
            	<!--   django- for loop -->
            	<div class="list-item">
                	<label class="checkbox">
                    	<input type="checkbox" class=" checkbox" name="checkedbox" value="{{ todo.id }}">
                    	<span class="complete-">{{ todo.title }}</span>
                	</label>
                	<span class=" category-{{ todo.category }} has-text-info">{{ todo.category }}</span>
                	<strong class="is-pulled-right"><i class="fa fa-calendar"></i>{{ todo.created }} -
                    	{{ todo.due_date }}</strong>
            	</div>
            	{% endfor %}
        	</div>
    	</form>
	</div>
</div>
{% endblock %}

And category.html. In it, we don’t have much that changes, it basically does not differ from todo.html:

{% extends 'base.html' %}
{% block content %}
<div class="columns has-background-link has-text-white is-centered" style="min-height: 101vh;">
	<div class="column is-half">
    	<h1 class="is-size-4 has-text-centered">    </h1>
    	<form action="" method="post">
        	{% csrf_token %}
        	<!-- csrf      -->
            <div class="field has-text-centered">
            	<label for="description" class="label has-text-white-bis">   </label>
            	<div class="control">
     	           <input type="text" id="description" class="input" placeholder="    ?"
                    	name="name" required>
            	</div>
            	<div class="field">
                	<button class="button is-primary" name="Add" type="submit">
                    	<span class="icon is-small">
                        	<i class="fa fa-plus"></i>
                    	</span>
                    	<span> </span>
                	</button>
                	<button class="button is-danger" name="Delete" formnovalidate="" type="submit">
                    	<span class="icon is-small">
           	             <i class="fa fa-trash-o"></i>
                    	</span>
	                    <span>   </span>
                	</button>
            	</div>
            </div>
 
            <!--  c   -->
            <div class="list is-hoverable">
            	{% for category in categories %}
            	<div class="list-item">
                	<label class="checkbox">
                    	<input type="checkbox" class="checkbox" name="check" value="{{category.id}}">
                    	<span class="complete-">{{ category.name }}</span>
                	</label>
            	</div>
            	{% endfor %}
 
        	</div>
    	</form>
	</div>
    {% endblock %}
 

Thanks to all! That's all. Perhaps, somewhere, the layout is not perfect, or there are other suggestions for improving the application, I'm waiting for everyone in the comment. By tradition, a few useful links:

  1. Django, 3
  2. ,
  3. PostgreSQL

All Articles