Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added apps/books/api/__init__.py
Empty file.
Empty file added apps/books/api/v1/__init__.py
Empty file.
36 changes: 36 additions & 0 deletions apps/books/api/v1/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from django_filters import CharFilter, DateFilter, FilterSet
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Протестистировать API наскольно получится подробно

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Подключить coverage и pytest

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поресерчить как вынести в readme процент покрытия

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


from ...models import Book


class BookFilter(FilterSet):
title = CharFilter(
lookup_expr="icontains",
)
author = CharFilter(
field_name="author__last_name",
lookup_expr="icontains",
)
publisher = CharFilter(
field_name="publisher__name",
lookup_expr="icontains",
)
tag = CharFilter(
field_name="tags__name",
lookup_expr="iexact",
)
language = CharFilter(
lookup_expr="iexact",
)
published_after = DateFilter(
field_name="published_at",
lookup_expr="gte",
)
published_before = DateFilter(
field_name="published_at",
lookup_expr="lte",
)

class Meta:
model = Book
fields = []
71 changes: 71 additions & 0 deletions apps/books/api/v1/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from rest_framework import serializers

from ...models import (
Author,
Book,
Comment,
Publisher,
Tag,
)


class PublisherSerializer(serializers.ModelSerializer):
class Meta:
model = Publisher
fields = "__all__"


class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = "__all__"


class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = "__all__"


class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(many=True, read_only=True)
publisher = PublisherSerializer(read_only=True)

class Meta:
model = Book
fields = [
"id",
"title",
"author",
"publisher",
"published_at",
]


class BookDetailSerializer(BookSerializer):
tags = TagSerializer(many=True, read_only=True)
comments = serializers.SerializerMethodField()

class Meta(BookSerializer.Meta):
fields = BookSerializer.Meta.fields + [
"description",
"isbn_code",
"total_pages",
"cover_image",
"language",
"tags",
"comments",
]

def get_comments(self, obj):
comments = obj.comments.all()[:5]
return CommentSerializer(comments, many=True).data


class CommentSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField()

class Meta:
model = Comment
fields = ["id", "text", "user", "created", "modified"]
read_only_fields = ["user", "created", "modified"]
22 changes: 22 additions & 0 deletions apps/books/api/v1/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.urls import include, path
from rest_framework import routers

from .views import (
AuthorViewSet,
BookViewSet,
CommentViewSet,
PublisherViewSet,
TagViewSet,
)

router = routers.DefaultRouter()

router.register(r"authors", AuthorViewSet)
router.register(r"books", BookViewSet)
router.register(r"comments", CommentViewSet)
router.register(r"publishers", PublisherViewSet)
router.register(r"tags", TagViewSet)

urlpatterns = [
path("", include(router.urls)),
]
55 changes: 55 additions & 0 deletions apps/books/api/v1/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets

from ...models import (
Author,
Book,
Comment,
Publisher,
Tag,
)
from .filters import BookFilter
from .serializers import (
AuthorSerializer,
BookDetailSerializer,
BookSerializer,
CommentSerializer,
PublisherSerializer,
TagSerializer,
)


class PublisherViewSet(viewsets.ModelViewSet):
queryset = Publisher.objects.all()
serializer_class = PublisherSerializer


class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer


class TagViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer


class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.select_related("publisher").prefetch_related(
"author__books", "tags"
)
filter_backends = [DjangoFilterBackend]
filterset_class = BookFilter

def get_serializer_class(self):
if self.action == "retrieve":
return BookDetailSerializer
return BookSerializer


class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.select_related("user", "book")
serializer_class = CommentSerializer

def perform_create(self, serializer):
serializer.save(user=self.request.user)
9 changes: 5 additions & 4 deletions apps/books/management/commands/parse_books.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import asyncio
from urllib.parse import urljoin

from asgiref.sync import sync_to_async
from django.core.management.base import BaseCommand
from urllib.parse import urljoin

from apps.books.models import Book, Author, Publisher
from apps.books.services.book_saver import BookSaver
from apps.books.models import Author, Book, Publisher
from apps.books.scrapers.base_scraper import BaseScraper
from apps.books.scrapers.piter_publ.book_parser import BookParser
from apps.books.scrapers.piter_publ.piter_scraper import PiterScraper
from apps.books.scrapers.base_scraper import BaseScraper
from apps.books.services.author_service import AuthorService
from apps.books.services.book_saver import BookSaver
from apps.books.services.publisher_service import PublisherService
from logger.books.log import get_logger

Expand Down
2 changes: 1 addition & 1 deletion apps/books/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db import models
from django.contrib.auth import get_user_model
from django.db import models
from django_extensions.db.models import TimeStampedModel

User = get_user_model()
Expand Down
2 changes: 1 addition & 1 deletion apps/books/scrapers/base_scraper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import httpx

import httpx
from bs4 import BeautifulSoup


Expand Down
5 changes: 3 additions & 2 deletions apps/books/scrapers/piter_publ/book_parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from bs4 import BeautifulSoup
from typing import List, Dict
from typing import Dict, List
from urllib.parse import urljoin

from bs4 import BeautifulSoup

from logger.books.log import get_logger

logger = get_logger(__name__)
Expand Down
5 changes: 3 additions & 2 deletions apps/books/scrapers/piter_publ/link_extractor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from typing import List
from urllib.parse import urljoin

from bs4 import BeautifulSoup

from logger.books.log import get_logger

Expand Down
3 changes: 2 additions & 1 deletion apps/books/scrapers/piter_publ/paginator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import re
from bs4 import BeautifulSoup
from urllib.parse import urljoin

from bs4 import BeautifulSoup

from logger.books.log import get_logger

logger = get_logger(__name__)
Expand Down
5 changes: 3 additions & 2 deletions apps/books/scrapers/piter_publ/piter_scraper.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import asyncio

from logger.books.log import get_logger

from ..base_scraper import BaseScraper
from .paginator import Paginator
from .link_extractor import LinkExtractor
from logger.books.log import get_logger
from .paginator import Paginator

logger = get_logger(__name__)
BASE_DOMAIN = "https://www.piter.com"
Expand Down
5 changes: 3 additions & 2 deletions apps/books/services/book_saver.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from datetime import datetime

from django.db import transaction
from pydantic import ValidationError

from apps.books.models import Book, Author, Publisher
from apps.books.validators.validators import BookInput
from apps.books.models import Author, Book, Publisher
from apps.books.services.author_service import AuthorService
from apps.books.services.publisher_service import PublisherService
from apps.books.validators.validators import BookInput
from logger.books.log import get_logger

logger = get_logger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion apps/books/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
from asgiref.sync import sync_to_async
from celery import shared_task

from apps.books.scrapers.piter_publ.piter_scraper import PiterScraper
from apps.books.management.commands.parse_books import (
AsyncBookFetcher,
book_saver,
logger,
)
from apps.books.scrapers.piter_publ.piter_scraper import PiterScraper


@shared_task
Expand Down
3 changes: 2 additions & 1 deletion apps/books/validators/validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pydantic import BaseModel, Field, field_validator
from typing import List, Optional

from pydantic import BaseModel, Field, field_validator


class AuthorInput(BaseModel):
first_name: str = ""
Expand Down
2 changes: 1 addition & 1 deletion config/celery_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from celery import Celery

from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")

Expand Down
21 changes: 18 additions & 3 deletions config/settings.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import os

from pathlib import Path
from celery.schedules import crontab

import environ
from celery.schedules import crontab

# Initialize environment variables
env = environ.Env()

BASE_DIR = Path(__file__).resolve().parent.parent

# Take environment variables from .env file
# Take environment variables from .env.example file
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))

# ========================
Expand All @@ -34,6 +33,8 @@
"django.contrib.staticfiles",
"apps.books.apps.BooksConfig",
"django_extensions",
"rest_framework",
"django_filters",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -121,6 +122,20 @@
# ====================
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"


# ====================
# DJANGO REST FRAMEWORK
# ====================
REST_FRAMEWORK = {
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
}

# ====================
# CELERY SETTINGS
# ====================
Expand Down
3 changes: 2 additions & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib import admin
from django.urls import path
from django.urls import include, path

urlpatterns = [
path("admin/", admin.site.urls),
path("api/v1/", include("apps.books.api.v1.urls")),
]