Социальная сеть, использующая Django и React

Недавно я разработал Restify, приложение для социальных сетей для ресторанов. Restify — это полнофункциональное приложение, использующее Django и React. Целью этого проекта была практика разработки полного стека. В этом посте я опишу, как работает приложение.

Модель данных

Restify — это социальная сеть для ресторанов. Пользователь может создать страницу ресторана, содержащую соответствующую основную информацию, пункты меню, изображения и сообщения в блоге.

Пользователи могут следить, ставить лайки и комментировать рестораны. Это приводит к множеству взаимосвязей между сущностями и отлично подходит для реляционной базы данных. Мы сохранили простой дизайн и использовали Sqlite3 вместе с Django Object-Relational Mapping (ORM) для базы данных. На следующей диаграмме сущность-связь показана модель данных.

Большинство сущностей имеют отношения с Рестораном. Некоторые из этих отношений являются отношениями «один ко многим»: в ресторане много уникальных пунктов меню, постов в блогах и изображений. Для этих отношений мы создали внешние ключи к объекту Restaurant. Например, вот определение объекта изображения ресторана:

class ImageModel(models.Model):
    ref_img = models.ImageField(upload_to='restaurant_pics/', null=True, blank=True)
    restaurant = models.ForeignKey(to=Restaurant, related_name='restaurant_images', on_delete=models.CASCADE)

В противоположность этому, отношения между пользователем и рестораном являются отношениями «многие ко многим». Эти отношения требуют дополнительных таблиц. Вот определение таблицы комментариев.

class Comment(models.Model):
    user = models.ForeignKey(to=ModifiedUser, related_name='comment', on_delete=models.CASCADE)
    timestamp = models.DateTimeField(auto_created=True, auto_now_add=True)
    restaurant = models.ForeignKey(to=Restaurant, related_name='restaurant_comment', on_delete=models.CASCADE)
    contents = models.CharField(max_length=250)

Эта таблица имеет внешние ключи как для пользователя, так и для объектов ресторана. Таблица также имеет дополнительные атрибуты для содержимого и метки времени.

Внутренняя структура

Серверная часть предоставляет интерфейс REST API. Мы использовали остальную структуру Django вместе с ее общими представлениями. Структура бэкэнда:

  • URL-адреса: точки входа для вызовов API. urls указывает путь к соответствующему представлению. Restify имеет три URL-адреса: администратор, рестораны и учетная запись.
  • Представления: конечные точки API. Эти классы наследуют универсальные API-классы остальных фреймворков Django и переопределяют атрибуты на основе бизнес-логики.
  • Модели: программные сущности. Django преобразует классы в таблицы базы данных. Атрибуты представляют поля базы данных.
  • Сериализаторы: объекты для преобразования сложных данных в собственные типы данных, которые можно преобразовать в JSON.

Административное управление

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

> python3 manage.py createsuperuser
Username: <your username goes here>
Email address: <your email goes here> 
Password: <your password goes here>
Password (again): <your password goes here>
Superuser created successfully.

Мы настроили маршрут для консоли администрирования. В файл urls мы добавили следующий код:

from django.contrib import admin
urlpatterns = [
    path('admin/', admin.site.urls),
    ...
]

Кроме того, мы зарегистрировали все модели в файле admin.py.

from django.contrib import admin
from restaurants.models import Blog, Comment, ImageModel, ModifiedUser, Restaurant, \
    MenuItem, Notification

admin.site.register(Restaurant)
admin.site.register(ModifiedUser)
admin.site.register(MenuItem)
admin.site.register(Notification)
admin.site.register(Comment)
admin.site.register(Blog)
admin.site.register(ImageModel)

Наконец, мы изменили settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    ...
]

После завершения настройки у нас был рабочий вид администратора. Вот скриншот:

Аутентификация

Мы реализовали аутентификацию с помощью токенов Json Web Tokens (JWT). Запрос на вход создает новый токен JWT. Затем токен отправляется во всех последующих запросах между клиентом и сервером. В settings.py мы настроили время жизни токена на сутки.

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
}

Структура внешнего интерфейса

Мы использовали React для интерфейса. Мы загрузили приложение с помощью Create React App. Мы также использовали Chakra UI для дизайна. Chakra UI — это библиотека компонентов React со встроенной поддержкой адаптивности, тем и специальных возможностей. Структура фронтенда такая:

  • App.js: настраивает маршруты к представлениям приложений. Начальный вид — это целевая страница.
  • Модули: представления приложений. Представления меняются в зависимости от типа пользователя (например, владельцы ресторана), поэтому мы используем хук, чтобы определить, что отображать.
  • Компоненты: многократно используемые части представлений. Компоненты заполняют данные, отправляя запрос API на серверную часть с помощью axios.
  • Хуки: пользовательские функции для подключения к состоянию React.

Пагинация

Restify реализует разбиение на страницы для большинства сущностей. На бэкэнде мы настроили settings.py, чтобы остальная структура поддерживала разбиение на страницы, используя:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    ...
}

Затем мы можем просто установить свойство pagination_class следующим образом:

class NotificationView(generics.ListAPIView):
    pagination_class = PageNumberPagination
    ...

На стороне фронтенда мы реализовали учебник по freecodecamp. Мы определили хук usePagination и компонент разбивки на страницы, чтобы использовать хук и отображать соответствующий диапазон. Вот пример пагинации:

Уведомления

Владельцы ресторанов получают уведомление, когда пользователь ставит лайк, комментирует или следит за их рестораном. Пользователи также получают уведомления, когда ресторан, за которым они следят, обновляет меню или публикует новый блог. Объект уведомления хранит уведомителя, получателя и другую соответствующую информацию. Уведомления в реальном времени вышли за рамки, поэтому вместо этого мы внедрили вытягивающую архитектуру с использованием REST.

Архитектура вытягивания требует, чтобы пользователи периодически вытягивали уведомления. Мы реализовали pull каждый раз, когда пользователь переходит на другую страницу. Вот внутренний интерфейс REST для уведомлений:

  • ПОЛУЧИТЬ: /accounts/notifications/?page=
  • ПАТЧ: /accounts/notifications/viewed/‹notification_number›
  • УДАЛИТЬ: /accounts/notifications/‹notification_number›

Когда пользователи впервые запускают страницу, запрос GET получает первое уведомление. На данный момент все уведомления непрочитаны. Как только пользователь нажимает на уведомление, мы отправляем запрос PATCH, чтобы пометить уведомление как просмотренное. Интерфейс временно помечает уведомление как проверенное.

Наконец, когда пользователь покидает страницу уведомлений или переходит на другую страницу, мы отправляем операцию DELETE всем проверенным уведомлениям.

Проблема CORS

Одной из проблем были проблемы с CORS, когда мы пытались соединить интерфейс и серверную часть. CORS расшифровывается как Cross-Origin Resource Sharing. Это механизм на основе заголовков HTTP, который позволяет серверу указывать действительные источники для загрузки ресурсов.

Пока наш сервер работал с Postman, мы не могли объединить части. Когда браузер отправлял запросы, запросы выдавали ошибку, указывающую, что доступ был заблокирован политикой CORS из-за отсутствия заголовка.

Мы исправили проблему CORS, установив django-cors-headers. Мы также настроили следующие изменения в settings.py:

INSTALLED_APPS = [
    'corsheaders'
]

MIDDLEWARE = [  
    'corsheaders.middleware.CorsMiddleware',
]
CORS_ALLOW_ALL_ORIGINS = True

Это настраивает CORS для разрешения всех источников, что было достаточно для локальной разработки.

Настройка документации

Мы также столкнулись с проблемой настройки документации Django. Мы настроили Swagger, добавив зависимость django-rest-swagger и настроив соответствующие параметры Swagger.

INSTALLED_APPS = [
    'rest_framework_swagger',
     ...
]
SWAGGER_SETTINGS = {
'USE_SESSION_AUTH': False,
"is_authenticated": True,  
"is_superuser": True,
'unauthenticated_user': 'django.contrib.auth.models.AnonymousUser',
}

Затем в файле URL-адресов мы добавили шаблон для доступа к swagger.

urlpatterns = [
    path('accounts/', include('accounts.urls', namespace='accounts')),
    ...
]

Это создало документацию в разделе http://localhost:8000/api/docs/. Вот как выглядела документация:

В документации отсутствовали подробности о кодах ответов и их описание. Конечная точка регистрации также возвращает 409, если имя пользователя уже существует. Та же проблема относится ко всем конечным точкам. Генерация схемы Rest Framework Django не поддерживает ответы.

У пользователя StackOverflow была такая же проблема. В настоящее время нет эффективного известного решения. В итоге мы взяли сгенерированный html из Swagger и вручную добавили все коды ответов.

В этом сообщении в блоге я описал детали реализации Restify, приложения социальной сети для ресторанов.

Создание Restify было отличным опытом обучения. Приятно видеть, как такие фреймворки, как React и Django, упрощают разработку. Благодаря достижениям в области искусственного интеллекта и таких приложений, как ChatGPT, многие из этих задач станут еще проще.

Исходный код доступен здесь.

Объем Restify включал сложную реляционную модель. Если вы хотите изучить разработку полного стека, я рекомендую выбрать меньший набор требований. Возьмите простое приложение, такое как список дел, и попробуйте разработать для него бэкенд и внешний интерфейс!