Changed work of shelf books

This commit is contained in:
maciejrusek
2026-03-22 14:19:17 +01:00
parent 5722f0b7ea
commit aeab43aa8a
23 changed files with 307 additions and 140 deletions

View File

@@ -1,3 +0,0 @@
shelves/id/items !!!!

View File

@@ -0,0 +1,25 @@
# Generated by Django 6.0.3 on 2026-03-22 12:16
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Author',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='authors', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 6.0.3 on 2026-03-22 12:16
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('authors', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Book',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('isbn', models.CharField(max_length=13)),
('authors', models.ManyToManyField(related_name='books', to='authors.author')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='books', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -1,6 +1,5 @@
from django.db import models from django.db import models
from apps.shelves.models import Shelf
from apps.authors.models import Author from apps.authors.models import Author
from apps.user.models import User from apps.user.models import User

View File

@@ -1,26 +1,17 @@
from rest_framework import serializers from rest_framework import serializers
from apps.shelves.serializers import ShelfSerializer
from apps.authors.serializers import AuthorSerializer from apps.authors.serializers import AuthorSerializer
from apps.books.models import Book from apps.books.models import Book
from apps.shelves.models import Shelf
from apps.authors.models import Author from apps.authors.models import Author
class BookSerializer(serializers.ModelSerializer): class BookSerializer(serializers.ModelSerializer):
shelf = ShelfSerializer(read_only=True)
shelf_id = serializers.PrimaryKeyRelatedField(
queryset=Shelf.objects.all(),
source="shelf",
write_only=True
)
authors = AuthorSerializer(many=True, read_only=True) authors = AuthorSerializer(many=True, read_only=True)
authors_id = serializers.PrimaryKeyRelatedField( authors_id = serializers.PrimaryKeyRelatedField(
queryset=Author.objects.all(), queryset=Author.objects.all(),
source="authors", source="authors",
many=True, many=True,
allow_null=True, write_only=True,
write_only=True required=False
) )
class Meta: class Meta:
@@ -28,27 +19,23 @@ class BookSerializer(serializers.ModelSerializer):
fields = [ fields = [
"id", "id",
"name", "name",
"isbn", "isbn",
"shelf",
"shelf_id",
"authors", "authors",
"authors_id" "authors_id"
] ]
read_only_fields = ["id"] read_only_fields = ["id"]
def __init__(self, instance=None, data=..., **kwargs): def __init__(self, *args, **kwargs):
super().__init__(instance, data, **kwargs) super().__init__(*args, **kwargs)
request = self.context.get("request") request = self.context.get("request")
if request and request.user: if request and request.user and request.user.is_authenticated:
self.fields["shelf_id"].queryset = Shelf.objects.filter(
user=request.user
)
self.fields["authors_id"].queryset = Author.objects.filter( self.fields["authors_id"].queryset = Author.objects.filter(
user=request.user user=request.user
) )
else:
self.fields["authors_id"].queryset = Author.objects.none()
def create(self, validated_data): def create(self, validated_data):

View File

@@ -14,6 +14,5 @@ class BooksViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return self.queryset.filter( return self.queryset.filter(user=self.request.user)
shelf__user_id=self.request.user.pk
).prefetch_related("authors")

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class ShelfBooksConfig(AppConfig):
name = 'apps.shelf_books'

View File

@@ -0,0 +1,29 @@
# Generated by Django 6.0.3 on 2026-03-22 12:16
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('books', '0001_initial'),
('shelves', '0002_initial'),
]
operations = [
migrations.CreateModel(
name='ShelfItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(default=0)),
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shelf_items', to='books.book')),
('shelf', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='book_items', to='shelves.shelf')),
],
options={
'constraints': [models.UniqueConstraint(fields=('book', 'shelf'), name='unique_book_per_shelf')],
},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 6.0.3 on 2026-03-22 12:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('books', '0001_initial'),
('shelf_books', '0001_initial'),
('shelves', '0002_initial'),
]
operations = [
migrations.RenameModel(
old_name='ShelfItem',
new_name='ShelfBook',
),
]

View File

@@ -4,7 +4,7 @@ from django.db import models
from apps.books.models import Book from apps.books.models import Book
from apps.shelves.models import Shelf from apps.shelves.models import Shelf
class ShelfItem(models.Model): class ShelfBook(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name="shelf_items") book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name="shelf_items")
shelf = models.ForeignKey(Shelf, on_delete=models.CASCADE, related_name="book_items") shelf = models.ForeignKey(Shelf, on_delete=models.CASCADE, related_name="book_items")

View File

@@ -0,0 +1,84 @@
from rest_framework import serializers
from apps.shelf_books.models import ShelfBook
from apps.books.models import Book
from apps.shelves.models import Shelf
from apps.shelves.serializers import ShelfSerializer
from apps.books.serializers import BookSerializer
class ShelfBookSerializer(serializers.ModelSerializer):
shelf = ShelfSerializer(read_only=True)
book = BookSerializer(read_only=True)
book_id = serializers.PrimaryKeyRelatedField(
queryset=Book.objects.all(),
source="book",
write_only=True
)
class Meta:
model = ShelfBook
fields = [
"id",
"shelf",
"book",
"book_id",
"quantity"
]
read_only_fields = ["id"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
request = self.context.get("request")
if request and request.user and request.user.is_authenticated:
self.fields["book_id"].queryset = Book.objects.filter(
user=request.user
)
else:
self.fields["book_id"].queryset = Book.objects.none()
def validate(self, attrs):
attrs = super().validate(attrs)
book = attrs.get("book")
shelf = self.context.get("shelf")
if not book:
book = self.instance.book
if not shelf:
return attrs
qs = ShelfBook.objects.filter(
book=book,
shelf=shelf
)
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise serializers.ValidationError(
"This Book already exists in shelf!"
)
if book.user_id != shelf.user_id:
raise serializers.ValidationError(
"Book and Shelf must be from the same user!"
)
return attrs
def create(self, validated_data):
shelf = self.context["shelf"]
validated_data["shelf"] = shelf
return super().create(validated_data)
def update(self, instance, validated_data):
shelf = self.context["shelf"]
validated_data["shelf"] = shelf
return super().update(instance, validated_data)

View File

@@ -0,0 +1,10 @@
from django.urls import path
from apps.shelf_books.views import ShelfBooks, SelectedShelfBook
urlpatterns = [
path("shelves/<int:shelf_id>/books/", ShelfBooks.as_view()),
path("shelves/<int:shelf_id>/books/<int:book_id>/", SelectedShelfBook.as_view())
]

View File

@@ -0,0 +1,84 @@
from rest_framework import generics
from apps.shelf_books.models import ShelfBook
from apps.shelves.models import Shelf
from apps.shelf_books.serializers import ShelfBookSerializer
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from drf_spectacular.utils import extend_schema, extend_schema_view
@extend_schema_view(
get=extend_schema(tags=["Shelf Books"]),
post=extend_schema(tags=["Shelf Books"]),
)
class ShelfBooks(generics.ListCreateAPIView):
queryset = ShelfBook.objects.all()
serializer_class = ShelfBookSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get_queryset(self):
shelf_id = self.kwargs["shelf_id"]
return ShelfBook.objects.filter(
shelf__id=shelf_id,
shelf__user_id=self.request.user.pk,
).select_related("book", "shelf").prefetch_related("book__authors")
def get_serializer_context(self):
context = super().get_serializer_context()
shelf_id = self.kwargs["shelf_id"]
shelf = Shelf.objects.filter(
id=shelf_id,
user=self.request.user
).first()
context["shelf"] = shelf
return context
@extend_schema_view(
get=extend_schema(tags=["Shelf Books"]),
put=extend_schema(tags=["Shelf Books"]),
patch=extend_schema(tags=["Shelf Books"]),
delete=extend_schema(tags=["Shelf Books"]),
)
class SelectedShelfBook(generics.RetrieveUpdateDestroyAPIView):
queryset = ShelfBook.objects.all()
serializer_class = ShelfBookSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
lookup_url_kwarg = "book_id"
def get_queryset(self):
return ShelfBook.objects.filter(
shelf_id=self.kwargs["shelf_id"],
shelf__user=self.request.user,
).select_related("book", "shelf").prefetch_related("book__authors")
def get_object(self):
queryset = self.get_queryset()
return generics.get_object_or_404(
queryset,
book_id=self.kwargs["book_id"]
)
def get_serializer_context(self):
context = super().get_serializer_context()
shelf = Shelf.objects.filter(
id=self.kwargs["shelf_id"],
user=self.request.user
).first()
context["shelf"] = shelf
return context

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class ShelfItemsConfig(AppConfig):
name = 'apps.shelf_items'

View File

@@ -1,77 +0,0 @@
from rest_framework import serializers
from apps.shelf_items.models import ShelfItem
from apps.books.models import Book
from apps.shelves.models import Shelf
from apps.shelves.serializers import ShelfSerializer
from apps.books.serializers import BookSerializer
class ShelfItemSerializer(serializers.ModelSerializer):
shelf = ShelfSerializer(read_only=True)
shelf_id = serializers.PrimaryKeyRelatedField(
queryset=Shelf.objects.all(),
source="shelf",
write_only=True
)
book = BookSerializer(read_only=True)
book_id = serializers.PrimaryKeyRelatedField(
queryset=Book.objects.all(),
source="book",
write_only=True
)
class Meta:
model = ShelfItem
fields = [
"id",
"shelf",
"shelf_id",
"book",
"book_id",
"quantity"
]
read_only_fields = ["id"]
def __init__(self, instance=None, data=..., **kwargs):
super().__init__(instance, data, **kwargs)
request = self.context.get("request")
if request and request.user:
self.fields["shelf_id"].queryset = Shelf.objects.filter(
user=request.user
)
self.fields["book_id"].queryset = Book.objects.filter(
user=request.user
)
def validate(self, attrs):
attrs = super().validate(attrs)
book = attrs.get("book")
shelf = attrs.get("shelf")
if book and shelf:
qs = ShelfItem.objects.filter(
book=book,
shelf=shelf
)
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise serializers.ValidationError(
"This Book already exists in shelf!"
)
if book.user_id != shelf.user_id:
raise serializers.ValidationError(
"Book and Shelf must be from the same user!"
)
return attrs

View File

@@ -1,9 +0,0 @@
from rest_framework.routers import DefaultRouter
from apps.shelf_items.views import ShelfItemsViewSet
urlpatterns = []
router = DefaultRouter()
router.register("", viewset=ShelfItemsViewSet)
urlpatterns += router.urls

View File

@@ -1,20 +0,0 @@
from rest_framework import viewsets
from apps.shelf_items.models import ShelfItem
from apps.shelf_items.serializers import ShelfItemSerializer
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
class ShelfItemsViewSet(viewsets.ModelViewSet):
queryset = ShelfItem.objects.all()
serializer_class = ShelfItemSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get_queryset(self):
return ShelfItem.objects.filter(
shelf__user_id=self.request.user.pk,
).select_related("book", "shelf").prefetch_related("book__authors")

View File

@@ -6,6 +6,7 @@ urlpatterns = [
path("shelves/", include("apps.shelves.urls")), path("shelves/", include("apps.shelves.urls")),
path("books/", include("apps.books.urls")), path("books/", include("apps.books.urls")),
path("authors/", include("apps.authors.urls")), path("authors/", include("apps.authors.urls")),
path("", include("apps.shelf_books.urls")),
path('schema/', SpectacularAPIView.as_view(), name='schema'), path('schema/', SpectacularAPIView.as_view(), name='schema'),
path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),

View File

@@ -1,4 +1,5 @@
import os import os
from datetime import timedelta
from config.env import BASE_DIR, env from config.env import BASE_DIR, env
@@ -21,6 +22,7 @@ INSTALLED_APPS = [
"apps.shelves.apps.ShelvesConfig", "apps.shelves.apps.ShelvesConfig",
"apps.books.apps.BooksConfig", "apps.books.apps.BooksConfig",
"apps.authors.apps.AuthorsConfig", "apps.authors.apps.AuthorsConfig",
"apps.shelf_books.apps.ShelfBooksConfig",
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
@@ -112,9 +114,18 @@ SPECTACULAR_SETTINGS = {
'DESCRIPTION': 'Library project', 'DESCRIPTION': 'Library project',
'VERSION': '1.0.0', 'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False, 'SERVE_INCLUDE_SCHEMA': False,
"COMPONENT_SPLIT_REQUEST": True,
"SCHEMA_PATH_PREFIX": "/api/",
} }
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
"REFRESH_TOKEN_LIFETIME": timedelta(days=7),
}
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/6.0/topics/i18n/ # https://docs.djangoproject.com/en/6.0/topics/i18n/