使用Django REST框架和Angular创建Todo应用
因为兴致使然,我尝试使用Django REST Framework来构建API,并使用Angular2来利用该API,创建了一个Todo应用程序,下面是一些笔记。

环境 –
-
- python 3.6.0
-
- node 7.5.0
- npm 4.1.2
Django
Django的安装配置
需要安装必要的库
$ pip install django
$ pip install djangorestframework
$ pip install django-filter
$ pip install django-cors-headers
创建项目
$ django-admin startproject django_app
创建应用程序
$ cd django_app
$ python manage.py startapp to_do
创建模型
用只包含标题和创建时间的模型简单进行。
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=140, blank=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
将应用程序反映到settings.py中。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'to_do', # 追加
]
移民()
$ python manage.py makemigrations
$ python manage.py migrate
使用SQLite这个无需准备的数据库。
创建管理员用户
$ python manage.py createsuperuser
随意设置用户名、电子邮件、密码。
管理员网站的设置。
from django.contrib import admin
from .models import Todo
@admin.register(Todo)
class Todo(admin.ModelAdmin):
pass
管理员网站的确认
python manage.py runserver
试着访问并登录到 http://localhost:8000/admin。


让我们从Todos的”Add”处随意注册一些待办事项。
进行Django REST Framework的配置并实现API。
加载REST框架
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'to_do',
'rest_framework', #追記
]
序列化器的定义
在django_app/to_do中创建serializer.py 文件。
from rest_framework import serializers
from .models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('id', 'title', 'created_at')
视图的定义
编辑 Django 应用中的 to_do/views.py 文件。
from django.shortcuts import render
import django_filters
from rest_framework import viewsets, filters
from .models import Todo
from .serializer import TodoSerializer
from rest_framework.decorators import api_view
class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all().order_by('-created_at')
serializer_class = TodoSerializer
URL模式的定义。
from django.conf.urls import url, include //includeを追記
from django.contrib import admin
from to_do.urls import router as to_do_router //追記
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include(to_do_router.urls)), //追記
]
由于应用程序的urls.py最初没有创建,因此需要创建它。
from rest_framework import routers
from .views import TodoViewSet
router = routers.DefaultRouter()
router.register(r'todo', TodoViewSet)
API操作确认
$ python manage.py runserver
访问 http://localhost:8000/api

XMLHTML的权限设置
为了从Angular访问预先设定好的API,我们将在settings.py中进行相应的设置。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'to_do',
'rest_framework',
'corsheaders', //追記
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware', //追記
'corsheaders.middleware.CorsMiddleware', //追記
]
〜中略〜
// 追記
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
角度
安装Angular CLI
$ npm install -g @angular/cli
Angular的安装配置
创建项目
$ ng new ng2app
稍等一下。
建造
$ cd ng2app
$ ng build
确认行动
$ ng serve
请尝试访问 http://localhost:4200
应该会出现一个显示“app works!”的页面。
显示Todo列表
首先,实现通过Django REST Framework生成的API获取并显示的功能。
模型的定义。(模型的定义)
在/ng2app/src/app的路径下创建一个名为models的文件夹,
在该文件夹中创建一个名为todo.model.ts的文件。
export class Todo {
id: number;
title: string;
}
在Django的模型中定义了created_at,但在前端中不需要,所以只需要定义id和title。
创建服务
在/ng2app/src/app目录下创建一个名为services的文件夹,在该文件夹中创建一个名为todo.service.ts的文件。
在这里,我们将实现获取API并传递数据的操作。
import { Injectable } from "@angular/core";
import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Todo } from '../models/todo.model';
@Injectable()
export class TodoService {
todo: Todo[] = [];
private Url = `http://127.0.0.1:8000/api/todo/`
private headers = new Headers({'Content-Type': 'application/json'});
constructor(
private http: Http
){}
// 全てのtodoをGETする
getAllTodo(): Promise<Todo[]> {
return this.http
.get(this.Url)
.toPromise()
.then(response => response.json() as Todo[])
.catch(this.handleError)
}
}
创建组件
在/ng2app/src/app的目录下创建一个名为components的文件夹,
在其中创建一个名为todo-list.component.ts的文件。
import { Component,Input } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { TodoService } from '../services/todo.service';
import { Todo } from '../models/todo.model';
@Component({
selector: 'todo-list',
templateUrl: '../templates/todo-list.component.html',
styleUrls: ['../static/todo-list.component.css']
})
export class TodoListComponent {
todos: Todo[] = [];
constructor(
private todoService: TodoService,
){}
ngOnInit(): void {
this.todoService.getAllTodo()
.then(todos => this.todos = todos);
}
}
创建HTML文件
在/ng2app/src/app目录下创建一个名为templates的文件夹,然后在该文件夹中创建todo-list.component.html文件。
<div class="container">
<div class="todo-list row">
<div *ngFor="let todo of todos" class="col-sm-8 col-sm-offset-2">
<div class="panel panel-default">
<div class="panel-body">
<span from="name">{{todo.title}}</span>
</div>
</div>
</div>
</div>
设置路由
在/ng2app/src/app中创建app-routing.module.ts。
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TodoListComponent } from './components/todo-list.component';
const routes: Routes = [
{ path: '', component: TodoListComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
当有访问 http://localhost:4200 时,让它去查看 TodoListComponent。
编辑app.component
请按照以下方式修改 /ng2app/src/app/app.component.ts 文件
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1 class="text-center">
<span class="title">{{ title }}</span>
<p class="sub-title">{{ subtitle }}</p>
</h1>
<router-outlet></router-outlet>
`,
styles: [
'.title { color: #ee6e73;}',
'.sub-title { font-size: small; }'
],
})
export class AppComponent {
title = 'Simple Todo';
subtitle = 'Angular2 + Django Rest Framework'
}
加载各种模块
编辑/ng2app/src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TodoListComponent } from './components/todo-list.component';
import { TodoService } from './services/todo.service';
@NgModule({
declarations: [
AppComponent,
TodoListComponent,
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule
],
providers: [TodoService],
bootstrap: [AppComponent]
})
export class AppModule { }
CSS文件的布局位置
在/ng2app/src/app中创建一个名为static的文件夹
创建todo-list.component.css文件
暂时按照下列内容进行编写
.todo-list {
padding-top: 10px;
}
.todo {
min-height: 130px;
}
.btn-circle {
width: 30px;
height: 30px;
text-align: center;
padding: 6px 0;
font-size: 12px;
line-height: 1.428571429;
border-radius: 15px;
}
.add-todo {
margin-top: 10px;
}
安装 bootstrap 模块
执行下面的指令
$ npm install --save bootstrap ng2-bootstrap
请注意,要编辑/ng2app/.angular-cli.json(这是一个隐藏文件)中的styles部分。
"styles": [
"styles.css",
"../node_modules/bootstrap/dist/css/bootstrap.css", //追記
],
确认行动
用以下指令启动应用程序
$ ng serve

我需要添加一个功能来创建新的Todo。
服务的编辑
在todo.service.ts文件的TodoService类中添加以下内容。
// 追加時の挙動
create(todo: Todo): Promise<Todo> {
return this.http
.post(this.Url, JSON.stringify(todo), {headers: this.headers})
.toPromise()
.then(res => res.json())
.catch(this.handleError);
}
// 追加された最新のtodoを一件取得する
getNewTodo(): Promise<Todo> {
return this.http
.get(this.Url+"?limit=1")
.toPromise()
.then(res => res.json().results)
.catch(this.handleError)
}
編輯元件
在todo.component.ts文件的TodoListComponent组件内添加以下内容。
export class TodoListComponent {
todos: Todo[] = [];
newtodos: Todo[] = []; //追記
@Input() todo: Todo = new Todo(); //追記
〜中略〜
// 保存ボタンを押した時の挙動
save(): void {
this.todoService
.create(this.todo)
.then(data => {this.getNewTodo()});
this.todo = new Todo();
}
// 最新の一件を呼び出す挙動
getNewTodo(): void {
this.todoService
.getNewTodo()
.then(res => {this.pushData(res)});
}
// htmlに渡すnewtodosにデータをpushする
pushData(data: Todo): void {
this.newtodos.unshift(data);
}
}
当添加新的待办事项时,通过save()函数执行TodoService中的create函数,将新的待办事项进行POST请求。
然后,通过getNewTodo()函数执行TodoService中的getNewTodo函数,获取最新的一件待办事项(即已进行POST请求的待办事项)。
将获取的一件待办事项通过pushData()函数存储到newtodos中,完成整个操作。
HTML的编辑
编辑 todo-list.component.html
<div class="container">
<!-- ここから -->
<div class="center">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<input [(ngModel)]="todo.title"
id="input_text"
type="text"
length="140"
class="form-control add-todo"
placeholder="add-todo"
(keydown.enter)="save()"
>
<button (click)="save()" class="btn btn-success pull-right add-todo">Add</button>
</div>
</div>
</div>
<div class="newtodo-list row" style="margin-top:10px;">
<div *ngFor="let newtodo of newtodos" class="col-sm-8 col-sm-offset-2">
<div class="panel panel-default">
<div class="panel-body">
<span from="name">{{newtodo[0].title}}</span>
</div>
</div>
</div>
</div>
<hr class="col-sm-8 col-sm-offset-2">
<!-- ここまで -->
<div class="todo-list row">
<div *ngFor="let todo of todos" class="col-sm-8 col-sm-offset-2">
<div class="panel panel-default">
<div class="panel-body">
<span from="name">{{todo.title}}</span>
</div>
</div>
</div>
</div>
确认行动
请使用以下命令启动应用程序。
$ ng serve

通过在输入部分添加新的待办事项
添加删除Todo的功能
服务的编辑
将以下内容添加到TodoService中的todo.service.ts文件中。
// 削除時の挙動
delete(id: number): Promise<void> {
const url = `${this.Url}${id}/`;
return this.http
.delete(url, {headers: this.headers})
.toPromise()
.then(() => null)
.catch(this.handleError);
}
元素的编辑
在todo.component.ts文件的TodoListComponent组件内添加下述内容。
// 削除ボタンを押した時の挙動
delete(id): void {
this.todoService
.delete(id);
}
编辑HTML
编辑todo-list.component.html文件。
<div class="container">
<div class="center">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<input [(ngModel)]="todo.title"
id="input_text"
type="text"
length="140"
class="form-control add-todo"
placeholder="add-todo"
(keydown.enter)="save()"
>
<button (click)="save()" class="btn btn-success pull-right add-todo">Add</button>
</div>
</div>
</div>
<div class="newtodo-list row" style="margin-top:10px;">
<div *ngFor="let newtodo of newtodos" class="col-sm-8 col-sm-offset-2">
<div [style.display]="!newtodo.hideElement ? 'inline':'none'">
<div class="panel panel-default">
<div class="panel-body">
<span from="name">{{newtodo[0].title}}</span>
<button (click)="delete(newtodo[0].id) || newtodo.hideElement=true"
type="button"
class="btn btn-success btn-circle pull-right"
>
<i class="glyphicon glyphicon-ok"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<hr class="col-sm-8 col-sm-offset-2">
<div class="todo-list row">
<div *ngFor="let todo of todos" class="col-sm-8 col-sm-offset-2">
<div [style.display]="!todo.hideElement ? 'inline':'none'">
<div class="panel panel-default">
<div class="panel-body">
<span from="name">{{todo.title}}</span>
<button (click)="delete(todo.id) || todo.hideElement=true"
type="button"
class="btn btn-success
btn-circle pull-right"
>
<i class="glyphicon glyphicon-ok"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
添加按钮,当按钮被按下时,调用删除方法并将元素设为display=none,我觉得这里不太好,想改一下。
确认动作
请使用以下命令启动应用程序
$ ng serve

可以使用复选框来删除待办事项。
增加编辑Todo的功能
服务编辑
请在todo.service.ts文件的TodoService类中添加以下内容。
// 更新時の挙動
update(todo: Todo): Promise<Todo> {
const url = `${this.Url}${todo.id}/`;
return this.http
.put(url, JSON.stringify(todo), {headers: this.headers})
.toPromise()
.then(res => res.json())
.catch(this.handleError);
}
组件的编辑
请在todo.component.ts文件的TodoListComponent组件内添加以下内容。
// todoを更新した時の挙動
update(id: number, title: string): void {
let todo = {
id: id,
title: title
}
this.todoService.update(todo);
}
因为这里也不太好,所以想要修复。
编辑HTML
编辑”todo-list.component.html”文件。
<div class="container">
<div class="center">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<input [(ngModel)]="todo.title"
id="input_text"
type="text"
length="140"
class="form-control add-todo"
placeholder="add-todo"
(keydown.enter)="save()"
>
<button (click)="save()" class="btn btn-success pull-right add-todo">Add</button>
</div>
</div>
</div>
<div class="newtodo-list row" style="margin-top:10px;">
<div *ngFor="let newtodo of newtodos" class="col-sm-8 col-sm-offset-2">
<div [style.display]="!newtodo.hideElement ? 'inline':'none'">
<div class="panel panel-default">
<div class="panel-body">
<span *ngIf="!newtodo.isEdit" (click)="newtodo.isEdit=true" from="name">{{newtodo[0].title}}</span>
<input *ngIf="newtodo.isEdit"
(focusout)="newtodo.isEdit=false || update(newtodo[0].id, newtodo[0].title)"
[(ngModel)]="newtodo[0].title"
id="input_text"
type="text"
length="140"
style="border:none; width:70%"
>
<button (click)="delete(newtodo[0].id) || newtodo.hideElement=true"
type="button"
class="btn btn-success btn-circle pull-right"
>
<i class="glyphicon glyphicon-ok"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<hr class="col-sm-8 col-sm-offset-2">
<div class="todo-list row">
<div *ngFor="let todo of todos" class="col-sm-8 col-sm-offset-2">
<div [style.display]="!todo.hideElement ? 'inline':'none'">
<div class="panel panel-default">
<div class="panel-body">
<span *ngIf="!todo.isEdit" (click)="todo.isEdit=true" from="name">{{todo.title}}</span>
<input *ngIf="todo.isEdit"
(focusout)="todo.isEdit=false || update(todo.id, todo.title)"
[(ngModel)]="todo.title"
id="input_text"
type="text"
length="140"
style="border:none; width:70%"
>
<button (click)="delete(todo.id) || todo.hideElement=true"
type="button"
class="btn btn-success
btn-circle pull-right"
>
<i class="glyphicon glyphicon-ok"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
当点击todo的字符串时,会切换到输入元素,当焦点离开输入元素时,更新操作将被执行。
确认行动
用下面的命令启动应用程序。
$ ng serve
应该有类似于开头的gif那样的动作。
这就是全部。
因为我是超级新手,如果有类似这样的事情,你能直接指出并告诉我吗?
另外,这里是源代码。欢迎进行Pull-Request。
未来
首先,我有打算在这个应用程序中引入用户认证之类的功能,并进行相关的工作。
参照的对象
使用Django REST Framework快速实现API – Qiita
我使用Rails 5 + Angular2 + TypeScript创建了一个Todo应用。 – DC4