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).
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 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.
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.
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.
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.
Once the project is created, change the settings in settings.py.
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.
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()
As other setting items, hurry up and edit LANGUAGE_CODE and TIME_ZONE.
django_test/settings.py
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
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.
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!"
After setting up the project, let's create an app.
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.
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',
]
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).
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.
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.
Run migrate once the migration file is generated.
python manage.py migrate
In actual development, "edit model" ⇒ "meke migration" ⇒ "migrate" will be repeated.
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 a user with the following command.
python manage.py createsuperuser
Set an appropriate password, email address, etc.
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.
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.
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.
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).
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.
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.
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.
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'))
]
Now, run runserver, access each URL pattern, and see how it works (whether the appropriate characters are displayed).
Then, is the implementation in views.py that was temporarily implemented this implementation? To do.
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.
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.
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 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.
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?
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))
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.
Now, finally, we will implement the delete function.
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')
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 %}
It's really a bonus, but I'll implement a detailed screen.
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})
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.
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.
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
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).
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 }}">«</a></li>
{% else %}
<li class="disabled"><a href="#">«</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 }}">»</a></li>
{% else %}
<li class="disabled"><a href="#">»</a></li>
{% endif %}
</ul>
{% endif %}
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).
I hope to add such things soon. For the time being.
Recommended Posts