Angular и Django: создание приложения микро-блога

Интересует тема, как вызывать функции API Angular 6 и HttpClient? В этом учебном пособии будут показаны некоторые методы построения приложения для микро-блогов, использующего Angular 6 и Django Rest Framework (DRF). В процессе мы узнаем следующее:

  • Как сделать бэкэнд приложение с помощью Django и API Django Rest Framework
  • Создание простого одностраничного приложения Angular 6, которое может запрашивать API
  • Аутентификация пользователей с помощью JSON Web Tokens (JWT)

Готовы? Давайте начнем!

Обзор

Это разделенное приложение, использующее два разных фреймворка. Таким образом, данные должны проходить через несколько шагов, чтобы перейти от базы данных к экрану пользователя. Вот простая диаграмма потока данных:

База данных SQLite → Модель Django → Сериализатор Django → Django ViewSet → Сервис Angular → Компонент Angular

В этом руководстве будет рассмотрено, как создать каждую часть нашего приложения для микро-блогов.

Приложение Django

Модели Django

Наше приложение микро-блога содержит две модели данных: User и BlogPost. Модель User предоставлена самим фреймворком Django, поэтому нам нужно создать только класс модели для BlogPost. Это довольно просто и определяет несколько полей, а также "волшебный" метод Python «to-string», называемый __str __().

microblog/models.py:

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
 
class BlogPost(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    date = models.DateTimeField(default=timezone.now)
    body = models.CharField(default='', max_length=200)
 
    def __str__(self):
        return self.body

После создания класса модели необходимо создать и провести миграции схемы БД.

Сериализатор

Django Rest Framework работает в концепции модели, сериализаторы и ViewSets. Сериализатор описывает, как преобразовать объекты модели в JSON и обратно. Django ORM использует классы моделей, а конечные точки API используют JSON. Сериализатор — это то, что преобразовывает одно в другое.

microblog/serializers.py:

from django.contrib.auth.models import User
from rest_framework import serializers
from .models import BlogPost
 
class UserSerializer(serializers.ModelSerializer):
 
    class Meta:
        model = User
        fields = ('id', 'username', 'first_name', 'last_name')
 
class BlogPostSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField(many=False)
 
    class Meta:
        model = BlogPost
        fields = ('id', 'user', 'date', 'body')

В отличие от наших вышеприведенных классов моделей, нам необходимо предоставить сериализатор для модели User. Django Rest Framework не делает это за нас, даже если сама модель User находится непосредственно в Django.

Кроме того, мы создаем класс BlogPostSerializer, который определяет, какие поля в модели BlogPost должны быть преобразованы в JSON. Обратите внимание, что мы также определяем связь с классом User. Это означает, что при сериализации объекта BlogPost полученный JSON будет содержать вложенный объект, который является сериализованной моделью пользователя. Представление JSON BlogPost может выглядеть так:

{
    "id": 1
    "user": {
        "id": 10,
        "username": "alice123",
        "first_name": "Alice",
        "last_name": "Smith"
    },
    "date": 1528071221,
    "body": "The quick brown fox jumped over the lazy dog"
}

ViewSet

Следующее, что нам нужно для Django Rest Framework - это ViewSet. Это коллекция Django Views (иными словами, контроллеров), которые содержатся в классе.

microblog/views.py:

from django.shortcuts import render
from django.contrib.auth.models import User
from rest_framework import viewsets, permissions
from .models import BlogPost
from . import serializers
from .permissions import ReadOnly
 
def index(request, path=''):
    return render(request, 'index.html')
 
class UserViewSet(viewsets.ModelViewSet):
    """
    Provides basic CRUD functions for the User model
    """
    queryset = User.objects.all()
    serializer_class = serializers.UserSerializer
    permission_classes = (ReadOnly, )
 
class BlogPostViewSet(viewsets.ModelViewSet):
    """
    Provides basic CRUD functions for the Blog Post model
    """
    queryset = BlogPost.objects.all()
    serializer_class = serializers.BlogPostSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
 
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

Также как и в сериализаторе, мы создаем ViewSet дял обеих моделей: User и BlogPost.

В этом примере приложение не позволяет редактировать пользовательские модели с помощью API, поэтому UserViewSet настроен с permission_classes = (ReadOnly, ), не позволяя API писать в БД. Как же происходит редактирование профилей пользователей, спросите вы? Это выходит за рамки этого примера, но встроенное приложение-админка Django предоставляет способ сделать это, если нужно. При создании своего собственного приложения вы можете создать компонент Angular, чтобы пользователи могли редактировать свои профили и изменять разрешение ReadOnly на другое разрешение, позволяя пользователям редактировать только свои собственные профили.

Класс BlogPostViewSet также содержит класс разрешений IsAuthenticatedOrReadOnly. Это позволяет посетителям просматривать любые записи в нашем приложении, но только пользователи, которые вошли в систему, могут создавать записи.

Конфигурация Django URL

Нам также необходимо добавить адреса ViewSet'ов в URL нашего приложения.

Во-первых, мы добавляем URL-адреса ViewSet в наше приложение для микроблогов:

microblog/urls.py

from django.urls import path, include
from rest_framework import routers
 
from . import views
 
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'posts', views.BlogPostViewSet)
router.register(r'users', views.UserViewSet)
 
urlpatterns = [
    path(r'api/', include(router.urls)),
    path(r'', views.index, name='index')
]

Во-вторых, добавим URL приложения в URL проекта:

angular_django_example/urls.py

...
urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'', include('microblog.urls')),   # add this
    path(r'api-token-auth/', obtain_jwt_token),
    path(r'api-token-refresh/', refresh_jwt_token),
]

Проверим наше API

Django Rest Framework поставляется с встроенным средством просмотра Swagger API. Чтобы узнать, что доступно в API, все, что вам нужно сделать, — запустить python manage.py runserver и посетить localhost:8000/api, чтобы увидеть конечные точки API в действии.

Вызов API из Angular

Мы завершили настройку бэкенд части приложения. Теперь пришло время создать наши компоненты и сервисы для Angular. Они вызовут конечные точки API, а также предоставят пользовательский интерфейс для приложения.

В предыдущем посте мы уже создали шаблоны Django, необходимые для обслуживания приложения Angular, поэтому мы можем погрузиться в сам код Angular.

Компонент и шаблон

В первой части руководства мы создали базовый компонент Angular. Сейчас нам нужно добавить несколько свойств и методов для работы с записями блога.

microblog/front-end/app.component.ts

import {Component, OnInit} from '@angular/core';
import {BlogPostService} from './blog_post.service';   # add this
import {UserService} from './user.service';
import {throwError} from 'rxjs';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
 
  /**
   * An array of all the BlogPost objects from the API
   */
  public posts;
 
  /**
   * An object representing the data in the "add" form
   */
  public new_post: any;
 
  constructor(private _blogPostService: BlogPostService, private _userService: UserService) { }
 
  ngOnInit() {
    this.getPosts();
    this.new_post = {};
    this.user = {
      username: '',
      password: ''
    };
  }
 
  ... user login/logout methods are unchanged from part 1. omitted for brevity ...
 
  getPosts() {
    this._blogPostService.list().subscribe(
      // the first argument is a function which runs on success
      data => {
        this.posts = data;
        // convert the dates to a nice format
        for (let post of this.posts) {
          post.date = new Date(post.date);
        }
      },
      // the second argument is a function which runs on error
      err => console.error(err),
      // the third argument is a function which runs on completion
      () => console.log('done loading posts')
    );
  }
 
  createPost() {
    this._blogPostService.create(this.new_post, this.user.token).subscribe(
       data => {
         // refresh the list
         this.getPosts();
         return true;
       },
       error => {
         console.error('Error saving!');
         return throwError(error);
       }
    );
  }
 
}

Наш компонент содержит два новых свойства: 'posts' представляет собой массив записей блога, который мы получили от API. Свойство 'new_post' содержит простой объект JavaScript, который используеся для создания формы новой записи блога.

Кроме того, есть еще два метода: getBlogPosts() будет запрашивать API через BlogPostService и заполнять объект 'posts', а createPost() берет данные из 'new_post' и отправляет их в функцию API.

В нашем файле шаблона Angular мы добавляем список pfgbctq в блоге и форму для создания нового сообщения.

microblog/front-end/app.component.html

...
<h2 class="mt-3">Micro Blog Posts</h2>
<div *ngFor="let post of posts">
  <div class="row mb-3">
    <label class="col-md-2">By:</label>
    <div class="col-md-2 mb-1">{{ post.user }}</div>
    <label class="col-md-2">Date:</label>
    <div class="col-md-6">{{ post.date }}</div>
    <div class="col-md-12">{{ post.body }}</div>
  </div>
</div>
 
<h3>Create a new post:</h3>
 
<div class="row mb-1">
  <label class="col-md-3">Enter your post:</label>
  <div class="col-md-9 mb-1"><input type="text" name="body" [(ngModel)]="new_post.body"></div>
  <div class="col-md-2 offset-3">
    <button (click)="createPost()" class="btn btn-primary">Save</button>
  </div>
</div>

Сервис Angular для одного сообщения блога

В 1-ой части мы создали класс UserService для Angular для обработки вызовов API, связанных с пользовательсокй авторизацией и работы с токеном. Теперь мы создадим вторую службу: BlogPostService, которая обрабатывает соединения с конечными точками API BlogPost, которые мы создали выше.

Вот заключительная часть нашей головоломки, сервис для сообщения блога:

microblog/front-end/blog_post.service.ts

import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {UserService} from './user.service';
 
@Injectable()
export class BlogPostService {
 
  constructor(private http: HttpClient, private _userService: UserService) { }
 
  // Uses http.get() to load data from a single API endpoint
  list() {
    return this.http.get('/api/posts');
  }
 
  // send a POST request to the API to create a new data object
  create(post, token) {
    httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': 'JWT ' + this._userService.token   // this is our token from the UserService (see Part 1)
      })
    };
    return this.http.post('/api/posts', JSON.stringify(post), httpOptions);
  }
}

Обратите внимание на методы list() и create() в нашем сервисе. Они создают Observable, используя HttpClient Angular и возвращают его вызывающему. Вот так компонент получает свои данные.

Использование токена CSRF

Помните, что в вышеприведенном коде ViewSet, конечные точки Blog Post имели разрешения только для проверки подлинности или чтения? Когда мы отправляем данные запросом POST в API, нам также нужен способ сообщить, что пользователь аутентифицирован.

Итак, давайте рассмотрим первую часть, где вызывали конечную точку API: аутентификацию, чтобы получить JSON Web Token. Мы можем использовать этот токен, чтобы указать API, что пользователь вошел в систему, а также идентификатор пользователя. Все, что нам нужно сделать, это добавить заголовок «Авторизация» в заголовки HTTP, которые мы отправляем с запросом API. При использовании JWT значение должно быть «JWT», за которым следует пробел, а затем весь токен.

Вот полный пример заголовка авторизации:

Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwib3JpZ19pYXQiOjE1MjgwNjg4NDEsInVzZXJfaWQiOjEsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTUyODA3MjQ0MX0.oxqzt-d2l5Bl473KwObsUetlKS2uOMXn7vqcHcSX5Gg

Обратите внимание, что в этом примере нам нужно отправить заголовок при записи в API. Поскольку анонимные пользователи могут видеть сообщения в блоге, и у них не будет токена JWT, мы не отправляем заголовок авторизации с запросами GET, создаваемыми методом list(). Однако в другом приложении, где контент не отображается анонимным пользователям, нам также необходимо отправить заголовок авторизации в запрос GET.

Перевод https://www.metaltoad.com/blog/angular-api-calls-django-part-2-building-micro-blog-app

Вернуться на верх