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
Вернуться на верх