使用Django REST框架和Angular创建Todo应用

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

todo_django_angular.gif

环境 –

    • 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。

スクリーンショット 2017-05-01 16.11.01.png
スクリーンショット 2017-05-01 16.11.43.png

让我们从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

スクリーンショット 2017-05-01 15.13.38.png

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
スクリーンショット 2017-05-01 16.57.51.png

我需要添加一个功能来创建新的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
スクリーンショット 2017-05-01 17.20.19.png

通过在输入部分添加新的待办事项

添加删除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
スクリーンショット 2017-05-01 17.36.41.png

可以使用复选框来删除待办事项。

增加编辑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

广告
将在 10 秒后关闭
bannerAds