[PYTHON] CRUD with Django

This is a memo when CRUDing with Django. I have extracted the content that suits me by referring to various sites.

Prior to development, please also refer to here, which summarizes the basic flow and concept of development with Django (although it covers a little content).

environment

OS

Since I am in a Mac environment, I am trying it on a Mac, but I think it is the same on basic Windows.

Python and virtual environment

Python is 3.6.1. I am creating a virtual environment using venv that comes standard with Python 3.x, but I think it does not have to be a virtual environment. Please see here for environment construction.

What you have installed with pip

The result of pip freeze is as follows.

Django==1.11
django-bootstrap-form==3.2.1
PyMySQL==0.7.11
pytz==2017.2

If you put Django in, pytz will be put in together, so there are three explicitly included: Django, django-bootstrap-form, and PyMySQL. Please install if necessary.

pip install Django
pip install django-bootstrap-form
pip install PyMySQL

django-bootstrap-from is a package for auto-generating forms in the Bootstrap style.

Creating and preparing a project

Django has the concept of "projects" and "applications." It's like creating a project first and then adding an application (hereafter referred to as an application) to it.

Is it like a Visual Studio solution and project? Like Ara from ASP.NET MVC.

Creating a project

Now let's create a project. Here we are creating a project called jango_test.

django-admin.py startproject django_test

When the creation is completed, the following file structure will be generated.

django_test/
    manage.py
    django_test/
        __init__.py
        settings.py
        urls.py
        wsgi.py

There is a directory with the same name as the project directly under the project directory. There is a configuration file common to the project in this (but it is confusing for explanation). As a general rule, the work explained here assumes work directly under the project directory.

settings.py settings

Once the project is created, change the settings in settings.py.

Database

Django is set to use SQLite by default, but since I use MySQL, I edit DATABASES accordingly.

django_test/settings.py


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'djangodb', #Rewrite
        'USER': 'root', #Rewrite
        'PASSWORD': 'root', #Rewrite
        'HOST': '',
        'PORT': '',
    }
}

It goes without saying that you need to set up MySQL before doing this.

Package import

At the beginning, I installed PyMySQL to connect with MySQL, but I can't use it by itself, so I load the package into the project. I wrote it at the beginning of settings.py.

There was an article that it was written in manage.py, but since it is related to settings, I wrote it in settings.py for the time being.

django_test/settings.py


import pymysql
pymysql.install_as_MySQLdb()

Other

As other setting items, hurry up and edit LANGUAGE_CODE and TIME_ZONE.

django_test/settings.py


LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'

Perform migration

Since the installation of the DB has been completed, we will migrate once here.

python manage.py migrate

As a result, the tables required to use Django will be generated in the specified DB.

Start the development server and check the operation

Now, let's start the development web server once and see if the Django screen can be viewed correctly. Start it with the following command.

python manage.py runserver

When the startup is complete, try accessing the URL below.

http://localhost:8000/

It's okay if you see something like "It worked!"

Creating and preparing an application

After setting up the project, let's create an app.

Creating an application

Here, let's create an app named crud.

python manage.py startapp crud

A directory called crud has been created at the same level as the working directory. App related files are generated below this.

Adding an application to a project

It seems that it can not be used just by generating the application, so I will describe it for loading into the project. Settings are made to settings.py. Also, load the django-bootstrap-form that you will use later.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+   'bootstrapform',
+   'crud',
]

Model definition and migration

Django uses an MTV model instead of MVC. However, there is no need to learn a new concept because it is just an image that V of MVC has been renamed to T (emplate) and C of VMC has been renamed to V (iew).

Describe the model

Create a Member model with the image of saving member information. Describe as follows in models.py under the crud directory.

crud/models.py


from django.db import models

# Register your models here
class Member(models.Model):
	name = models.CharField('Full name', max_length=255)
	email = models.CharField('E-Mail', max_length=255)
	age = models.IntegerField('age', blank=True, default=0)

	def __str__(self):
		return self.name

I won't explain it in particular, but I think it's within the range that I can imagine.

Create migration file

After writing the model, generate a migration file for table generation that holds the model. It's a so-called code first. By executing make migrations by specifying the application name, it seems that a migration file is created by scanning the model changes in the target application.

python manage.py makemigrations crud

It seems that it can be used with existing tables.

migration

Run migrate once the migration file is generated.

python manage.py migrate

In actual development, "edit model" ⇒ "meke migration" ⇒ "migrate" will be repeated.

Enable management site and add test data

Django allows you to access the (model) admin site from the beginning by visiting http: // localhost: 8000 / admin. However, since the user and password for logging in to the management site are not set, first generate and obtain it.

Add user

Add a user with the following command.

python manage.py createsuperuser

Set an appropriate password, email address, etc.

Addition of managed model (Member)

When you log in to the management site, only "Group" and "User" are displayed as management targets by default. To manage your own model, you need to add the necessary description to admin.py under the app directory.

crud/admin.py


from django.contrib import admin
from crud.models import Member

# Register your models here.
admin.site.register(Member)

Please update after adding the description, or log in again to check the changes.

Add data

Once you can manage Members on the admin site, add a few lines of data for later testing.

Note that Django has a feature called firexure to import data.

View and routing settings

In the order of MVT, I would like to play with Templete next, but in order to understand the structure of the application, set View (equivalent to Controller) and routing first. This time, I will set it so that each function can be accessed with the following URL pattern.

Create a View template

Now, let's write View first. However, before implementing a specific function, I would like to implement a method that simply returns a character string, such as "list", "edit", and "delete", and check the mapping between the URL and the function.

The reason why there is no "new" is that, like other frameworks, if there is a POST and an ID, it will be edited, and if it is just a POST, it will be judged as a new addition (because "edit" is used in common).

Creating a template views.py

The views.py under crud should be as follows.

crud/views.py


from django.shortcuts import render
from django.http import HttpResponse


#List
def index(request):
    return HttpResponse("List")
    
#New and edit
def edit(request, id=None):
	return HttpResponse("Edit")

#Delete
def delete(request, id=None):
	return HttpResponse("Delete")

#Details (bonus)
def detail(request, id=None):
    return HttpResponse("Details")

It's just a process that returns a string.

Set up routing

To set the routing in Django, it seems to be a good practice to generate urls.py under the application directory, set the routing in the application, and then include it in urls.py of the entire project and set it. is.

In-app routing

Set up routing according to the URL and feature map rules shown above.

crud/urls.py


from django.conf.urls import url
from crud import views

urlpatterns = [
    url(r'^members/$', views.index, name='index'),
    url(r'^members/add/$', views.edit, name='add'),
    url(r'^members/edit/(?P<id>\d+)/$', views.edit, name='edit'),
    url(r'^members/delete/(?P<id>\d+)/$', views.delete, name='delete'),
    url(r'^members/detail/(?P<id>\d+)/$', views.detail, name='detail'),
]

Routing is

url(r'URL pattern regular expression', views.Corresponding method of py, name='Route name')

The format is.

The description> r'' seems to mean that special characters are not escaped in''. The route name is used when specifying the link destination in the template.

Project-wide routing

Include the urls.py set in the app directory. This will result in a URL like http: // localhost: 8000 / crud / members /.

urls.py


from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^crud/', include('crud.urls', namespace='crud'))
]

Check the operation

Now, run runserver, access each URL pattern, and see how it works (whether the appropriate characters are displayed).

Implementation of list function

Then, is the implementation in views.py that was temporarily implemented this implementation? To do.

Edit views.py (implementation of list)

Describe index () as follows.

crud/views.py


from django.shortcuts import render
from django.http import HttpResponse

from crud.models import Member #add to

#List
def index(request):
    members = Member.objects.all().order_by('id') #Get value
    return render(request, 'members/index.html', {'members':members}) #Pass a value to Template

I'm getting a value for members and passing it to members / index.html with the name members.

Preparation of common template

Before creating index.html on the receiving side, create the common part on each page as a common template (base.html). This time, we will use Bootstrap as a CSS framework, so we are loading the necessary CSS and JS from the CDN.

Create a directory called Templates under the application directory (crud) and save it as base.html.

crud/templates/base.html


{% load staticfiles %}
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}My books{% endblock %}</title>
    <!-- Bootstrap -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      {% block content %}
        {{ content }}
      {% endblock %}
    </div>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!--Where to write js-->
    {% block script %}
    {% endblock %}
  </body>
</html>

This completes the common template. Describe the content corresponding to {%%} in the template on a separate page.

Creating an individual page (index.html)

Now let's create a separate page. Create a members directory under the templates directory created earlier and save it as index.html in it.

Django doesn't seem to have much restrictions on the directory name where templates are stored. In the examples on the net, many of them are called app names (crud here). The official tutorial seems to do the same.

crud/templates/members/index.html


{% extends "base.html" %}

{% block title %}
Scheduled to display a list
{% endblock title %}

{% block content %}
<h3>List display</h3>

<a href="{% url 'crud:add' %}" class="btn btn-primary btn-sm">add to</a>

<table class="table table-striped table-bordered">
    <thead>
        <tr>
            <th>ID</th>
            <th>Full name</th>
            <th>E-Mail</th>
            <th>age</th>
            <th>operation</th>
        </tr>
    </thead>
    <tbody>
        {% for member in members %}
        <tr>
            <td>{{ member.id }}</td>
            <td><a href="{% url 'crud:detail' id=member.id %}">{{ member.name }}</a></td>
            <td>{{ member.email }}</td>
            <td>{{ member.age }}</td>
            <td>
                <a href="{% url 'crud:edit' id=member.id %}" class="btn btn-primary btn-sm">Edit</a>
                <a href="{% url 'crud:delete' id=member.id %}" class="btn btn-primary btn-sm" id="btn_del">Delete</a>
            </td>
        </tr>
        {% endfor %}
    </tbody>
</table>
<!--Put pagination here-->

{% endblock content %}

<!--Insert js-->
{% block script %}
{% endblock %}

Check the operation

Check if the list screen is displayed correctly after each implementation is completed. Click to see if the "Register", "Edit", and "Delete" button links are appropriate.

Implement new addition / editing functions

Next, implement the new addition / editing function. New and edit use the same views.py and template. In Django, it seems that it is common to use Form and ModelForm classes that automatically generate form (HTML information), so let's use it.

The Form class seems to be used to create forms (such as search) without a Model.

Now, create forms.py under the app directory and write as follows.

crud/forms.py


from django.forms import ModelForm
from crud.models import Member


class MemberForm(ModelForm):
	class Meta:
		model = Member
		fields = ('name','email','age',)

Aside from the details, I'll use the Member model and use name, email and age! Is it like a declaration?

Editing views.py (implementation of new / editing)

crud/views.py


from django.shortcuts import render, get_object_or_404, redirect #add to
from django.http import HttpResponse

from crud.models import Member
from crud.forms import MemberForm #add to

#(Excerpt)
#New and edit
def edit(request, id=None):

	if id: #When there is an id (when editing)
		#Search by id and return results or 404 error
		member = get_object_or_404(Member, pk=id)
	else: #When there is no id (when new)
		#Create Member
		member = Member()

	#At POST (when the registration button is pressed, whether new or edit)
	if request.method == 'POST':
		#Generate form
		form = MemberForm(request.POST, instance=member)
		if form.is_valid(): #Save if validation is OK
			member = form.save(commit=False)
			member.save()
			return redirect('crud:index')
	else: #At the time of GET (generate form)
		form = MemberForm(instance=member)
	
	#Display new / edit screen
	return render(request, 'members/edit.html', dict(form=form, id=id))

Preparation of new / edit screen

Create a screen (edit.html) to be used when new / editing.

crud/templates/members/edit.html


{% extends "base.html" %}
{% load bootstrap %}

{% block title %}Member editing{% endblock title %}

{% block content %}
    {% if id %}
    <h3 class="page-header">Member editing</h3>
    <form action="{% url 'crud:edit' id=id %}" method="post" class="form-horizontal" role="form">
    {% else %}
    <h3 class="page-header">Member registration</h3>
    <form action="{% url 'crud:add' %}" method="post" class="form-horizontal" role="form">
    {% endif %}
      {% csrf_token %}
      {{ form|bootstrap_horizontal }}
      <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
          <button type="submit" class="btn btn-primary">Send</button>
        </div>
      </div>
    </form>
    <a href="{% url 'crud:index' %}" class="btn btn-default btn-sm">Return</a>
{% endblock content %}

It may be better to change the title by conditional branching. Now, check the operation.

Implement the delete function

Now, finally, we will implement the delete function.

Erase for the time being

I will erase it for the time being. I'm really angry if I don't implement the delete process in POST, but that's another time.

crud/views.py


def delete(request, id):
	# return HttpResponse("Delete")
	member = get_object_or_404(Member, pk=id)
	member.delete()
	return redirect('crud:index')

Confirm when deleting

At the very least, check it when deleting. Add the following description to index.html. It should be just before {% endblock content%}.

members/index.html


{% block script %}
<script>
    $(function(){
        $("#btn_del").click(function(){
            if(confirm("Do you want to delete it?")){
                //Processing yes (go ahead without doing anything)
            }else{
                //Cancel processing
                return false;
            }
        });
    })
</script>
{% endblock %}

Implement the detailed display function

It's really a bonus, but I'll implement a detailed screen.

Edit views.py

As with editing and deleting, it receives the id, searches, and passes the result to the template.

views.py


#Details
def detail(request, id=id):
    member = get_object_or_404(Member, pk=id)
    return render(request, 'members/detail.html', {'member':member})

Preparation of detail screen

Expand the received member. I think it's better to set up a table, but that's not the point, so just display it for the time being.

members/detail.html


{% extends "base.html" %}
{% load bootstrap %}

{% block title %}Member details{% endblock title %}

{% block content %}
    <h3>Detailed information of members</h3>
    <h5>name</h5>
    {{ member.name }}
    <h5>E-Mail</h5>
    {{ member.email }}
    <h5>age</h5>
    {{ member.age }}
    <br>
    <br>
    <a href="{% url 'crud:index' %}" class="btn btn-default btn-sm">Return</a>
{% endblock content %}

With the above, I tried to implement the CRUD function.

application

Pagination

Pagination is an integral part of any web app. Also, each framework provides a way to make it easier. In Django, it seems that it is common to use LitView, so let's use it. ListView is a kind of class-based general-purpose view, and it seems to be a View class that provides general-purpose functions according to various purposes.

Edit views.py (rewrite index method)

You can rewrite the index method, but for comparison, add the MemberList () class without changing the index method.

views.py


from django.views.generic import ListView #add to


#List
def index(request):
    members = Member.objects.all().order_by('id') #Get value
    return render(request, 'members/index.html', {'members':members}) #Pass a value to Template
    
#List (added for pagination)
class MemberList(ListView):
	model = Member #Model to use
	context_object_name='members' #Object name setting (by default object_It becomes a list)
	template_name='members/index.html' #Specifying a template page
	paginate_by = 1 #Number of pages per page

Editing routing

Once added, edit the routing to use MemberList () instead of index without paging.

urls.py


from django.conf.urls import url
from crud import views

urlpatterns = [
    #url(r'^members/$', views.index, name='index'), #Comment out
    url(r'^members/$', views.MemberList.as_view(), name='index'), #add to
    url(r'^members/add/$', views.edit, name='add'),
    url(r'^members/edit/(?P<id>\d+)/$', views.edit, name='edit'),
    url(r'^members/delete/(?P<id>\d+)/$', views.delete, name='delete'),
]

At this point, you should see only one line. (Without forward / return function).

Add paging part

Write HTML elements for paging. In addition, a class element is added here so that the Bootstrap paging element can be rendered correctly.

Put the following content in \ <!-Put pagination here-> in index.html.

members/index.html


<!--Pagination (added below)-->
{% if is_paginated %}
<ul class="pagination">
  <!--Return<<Display processing-->
  {% if page_obj.has_previous %}
    <li><a href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li>
  {% else %}
    <li class="disabled"><a href="#">&laquo;</a></li>
  {% endif %}
  <!--Page display (if there are many, separate processing is required) -->
  {% for linkpage in page_obj.paginator.page_range %}
    {% ifequal linkpage page_obj.number %}
      <li  class="active"><a href="#">{{ linkpage }}</a></li>
    {% else %}
      <li><a href="?page={{ linkpage }}">{{ linkpage }}</a></li>
    {% endifequal %}
  {% endfor %}
  <!--next>>Display processing-->
  {% if page_obj.has_next %}
    <li><a href="?page={{ page_obj.next_page_number }}">&raquo;</a></li>
  {% else %}
    <li class="disabled"><a href="#">&raquo;</a></li>
  {% endif %}
</ul>
{% endif %}

Validation

Next, let's take a brief look at validation. By default, the column set in the model? It seems that the minimum validation (max_length etc.) is set according to the information. Also, it seems that basic input is required.

This is not enough, so I will add it. The addition seems to mess with forms.py. In the example below

It's like that.

crud/forms.py


from django.forms import ModelForm
from crud.models import Member
from django import forms
import re


class MemberForm(ModelForm):

	#Override the conditions defined in the model? (Write before the description of Meta)
	name = forms.CharField(required=False,max_length=8)

	class Meta:
		model = Member
		fields = ('name','email','age',)

	#Validation of each element
	def clean_email(self):
		email = self.cleaned_data['email']
		#But for the time being
		if re.match(r'.+@+',email) == None:
			raise forms.ValidationError("It is not an email address.")
		return email

It seems that validation for each element can be added by defining a method called clear_element name ().

The meaning of> clear ~ seems to mean data that has passed basic validation (clear).

Other

I hope to add such things soon. For the time being.

Recommended Posts

CRUD with Django
Internationalization with django
CRUD GET with Nuxt & Django REST Framework ②
CRUD POST with Nuxt & Django REST Framework
CRUD GET with Nuxt & Django REST Framework ①
Authenticate Google with Django
Django 1.11 started with Python3.6
Upload files with Django
Development digest with Django
Output PDF with Django
Markdown output with Django
Use Gentelella with django
Twitter OAuth with Django
Send email with Django
File upload with django
Use LESS with Django
Pooling mechanize with Django
Use MySQL with Django
Start today with Django
Getting Started with Django 2
CRUD PUT, DELETE with Nuxt & Django REST Framework
Get started with Django! ~ Tutorial ⑤ ~
[S3] CRUD with S3 using Python [Python]
Create an API with Django
Do Django with CodeStar (Python3.8, Django2.1.15)
Deploy Django serverless with Lambda
Python3 + Django ~ Mac ~ with Apache
Getting Started with Python Django (1)
Create a homepage with django
Get started with Django! ~ Tutorial ④ ~
Django
Getting Started with Python Django (4)
Web application creation with Django
Getting Started with Python Django (3)
Save tweet data with Django
Do AES encryption with DJango
Getting Started with Python Django (6)
Combine two images with Django
Getting Started with Django with PyCharm
Real-time web with Django Channels
Double submit suppression with Django
Django REST framework with Vue.js
Use prefetch_related conveniently with Django
Getting Started with Python Django (5)
Login with django rest framework
Qiita API Oauth with Django
Test Driven Development with Django Part 3
reload in django shell with ipython
Steps to develop Django with VSCode
Test Driven Development with Django Part 4
Load Django modules with an interpreter
Set up social login with Django
Test Driven Development with Django Part 6
Manage Django config files with Python-decouple
Deploy a Django application with Docker
Common html to rent with Django
Django Model with left outer join
Test Driven Development with Django Part 2
Django Tips-Create a ranking site with Django-
Twitter posting application made with Django
Automatically generate model relationships with Django