add review api

This commit is contained in:
Your Name 2023-06-03 01:22:19 -04:00 committed by Henri Dickson
parent a7857d99b2
commit f92861105d
7 changed files with 158 additions and 64 deletions

View file

@ -48,8 +48,9 @@ Please see [doc/install.md](doc/install.md)
- to file a bug or request new features for NeoDB, please contact NeoDB on [Fediverse](https://mastodon.social/@neodb) or [Twitter](https://twitter.com/NeoDBsocial)
## Contribution
- Please see [doc/development.md](doc/development.md) for some basics to start with
- Join our Discord community, links available on [our Fediverse profile](https://mastodon.social/@neodb)
- To build application with NeoDB API, documentation is available in [NeoDB API Developer Console](https://neodb.social/developer/)
- To help develop NeoDB, please see [doc/development.md](doc/development.md) for some basics to start with
- Join our Discord community to share your ideas/questions/creations, links available on [our Fediverse profile](https://mastodon.social/@neodb)
## Sponsor
If you like this project, please consider sponsoring

View file

@ -45,8 +45,10 @@ class RedirectedResult(Schema):
class PageNumberPagination(NinjaPageNumberPagination):
items_attribute = "data"
class Output(Schema):
items: List[Any]
data: List[Any]
pages: int
count: int
@ -57,6 +59,7 @@ class PageNumberPagination(NinjaPageNumberPagination):
**params: Any,
) -> Output:
val = super().paginate_queryset(queryset, pagination, **params)
val["data"] = val["items"]
val["pages"] = (val["count"] + self.page_size - 1) // self.page_size
return val

View file

@ -28,13 +28,13 @@
<h5>Developer Console</h5>
<details {% if token %}open{% endif %}>
<summary>
<b>Access Token</b>
<b>Access Token Management</b>
</summary>
<form method="post" role="group">
{% csrf_token %}
<input type="text"
readonly
value="{{ token | default:'Token will only be shown once here, previous tokens will be cleared. If you lose it, you can generate a new one.' }}">
value="{{ token | default:'Once generated, token will only be shown once here, previous tokens will be revoked.' }}">
<input type="submit" value="Generate" />
</form>
<p>

View file

@ -32,8 +32,7 @@ class MarkInSchema(Schema):
post_to_fediverse: bool = False
@api.get("/me/shelf", response={200: List[MarkSchema], 403: Result})
@protected_resource()
@api.get("/me/shelf/{type}", response={200: List[MarkSchema], 401: Result, 403: Result})
@paginate(PageNumberPagination)
def list_marks_on_shelf(
request, type: ShelfType, category: AvailableItemCategory | None = None
@ -42,7 +41,7 @@ def list_marks_on_shelf(
Get holding marks on current user's shelf
Shelf's `type` should be one of `wishlist` / `progress` / `complete`;
`category` is optional, all marks will be returned if not specified.
`category` is optional, marks for all categories will be returned if not specified.
"""
queryset = request.user.shelf_manager.get_latest_members(
type, category
@ -52,10 +51,8 @@ def list_marks_on_shelf(
@api.get(
"/me/shelf/item/{item_uuid}",
response={200: MarkSchema, 403: Result, 404: Result},
auth=OAuthAccessTokenAuth(),
response={200: MarkSchema, 401: Result, 403: Result, 404: Result},
)
# @protected_resource()
def get_mark_by_item(request, item_uuid: str):
"""
Get holding mark on current user's shelf by item uuid
@ -70,9 +67,9 @@ def get_mark_by_item(request, item_uuid: str):
@api.post(
"/me/shelf/item/{item_uuid}", response={200: Result, 403: Result, 404: Result}
"/me/shelf/item/{item_uuid}",
response={200: Result, 401: Result, 403: Result, 404: Result},
)
@protected_resource()
def mark_item(request, item_uuid: str, mark: MarkInSchema):
"""
Create or update a holding mark about an item for current user.
@ -102,9 +99,9 @@ def mark_item(request, item_uuid: str, mark: MarkInSchema):
@api.delete(
"/me/shelf/item/{item_uuid}", response={200: Result, 403: Result, 404: Result}
"/me/shelf/item/{item_uuid}",
response={200: Result, 401: Result, 403: Result, 404: Result},
)
@protected_resource()
def delete_mark(request, item_uuid: str):
"""
Remove a holding mark about an item for current user.
@ -118,16 +115,106 @@ def delete_mark(request, item_uuid: str):
return 200, {"message": "OK"}
# @api.get("/me/review/")
# @api.get("/me/review/item/{item_uuid}")
# @api.post("/me/review/item/{item_uuid}")
# @api.delete("/me/review/item/{item_uuid}")
class ReviewSchema(Schema):
visibility: int = Field(ge=0, le=2)
item: ItemSchema
created_time: datetime
title: str
body: str
html_content: str
class ReviewInSchema(Schema):
visibility: int = Field(ge=0, le=2)
created_time: datetime | None
title: str
body: str
post_to_fediverse: bool = False
@api.get("/me/review/", response={200: List[ReviewSchema], 401: Result, 403: Result})
@paginate(PageNumberPagination)
def list_reviews(request, category: AvailableItemCategory | None = None):
"""
Get reviews by current user
`category` is optional, reviews for all categories will be returned if not specified.
"""
queryset = Review.objects.filter(owner=request.user)
if category:
queryset = queryset.filter(query_item_category(category))
return queryset.prefetch_related("item")
@api.get(
"/me/review/item/{item_uuid}",
response={200: ReviewSchema, 401: Result, 403: Result, 404: Result},
)
def get_review_by_item(request, item_uuid: str):
"""
Get review on current user's shelf by item uuid
"""
item = Item.get_by_url(item_uuid)
if not item:
return 404, {"message": "Item not found"}
review = Review.objects.filter(owner=request.user, item=item).first()
if not review:
return 404, {"message": "Review not found"}
return review
@api.post(
"/me/review/item/{item_uuid}",
response={200: Result, 401: Result, 403: Result, 404: Result},
)
def review_item(request, item_uuid: str, review: ReviewInSchema):
"""
Create or update a review about an item for current user.
`title`, `body` (markdown formatted) and`visibility` are required;
`created_time` is optional, default to now.
if the item is already reviewed, this will update the review.
"""
item = Item.get_by_url(item_uuid)
if not item:
return 404, {"message": "Item not found"}
Review.review_item_by_user(
item,
request.user,
review.title,
review.body,
review.visibility,
created_time=review.created_time,
share_to_mastodon=review.post_to_fediverse,
)
return 200, {"message": "OK"}
@api.delete(
"/me/review/item/{item_uuid}",
response={200: Result, 401: Result, 403: Result, 404: Result},
)
def delete_review(request, item_uuid: str):
"""
Remove a review about an item for current user.
"""
item = Item.get_by_url(item_uuid)
if not item:
return 404, {"message": "Item not found"}
Review.review_item_by_user(item, request.user, None, None)
return 200, {"message": "OK"}
# @api.get("/me/collection/")
# @api.post("/me/collection/")
# @api.get("/me/collection/{uuid}")
# @api.post("/me/collection/{uuid}")
# @api.put("/me/collection/{uuid}")
# @api.delete("/me/collection/{uuid}")
# @api.get("/me/collection/{uuid}/items")
# @api.post("/me/collection/{uuid}/items")
# @api.delete("/me/collection/{uuid}/items")
# @api.patch("/me/collection/{uuid}/items")
# @api.get("/me/collection/{uuid}/item/")
# @api.post("/me/collection/{uuid}/item/")
# @api.get("/me/tag/")
# @api.post("/me/tag/")
# @api.get("/me/tag/{title}")
# @api.put("/me/tag/{title}")
# @api.delete("/me/tag/{title}")

View file

@ -1,5 +1,6 @@
from django.db import models
from polymorphic.models import PolymorphicModel
from mastodon.api import share_review
from users.models import User
from catalog.common.models import Item, ItemCategory
from .mixins import UserOwnedObjectMixin
@ -286,31 +287,36 @@ class Review(Content):
def rating_grade(self):
return Rating.get_item_rating_by_user(self.item, self.owner)
@staticmethod
def review_item_by_user(item, user, title, body, metadata={}, visibility=0):
# allow multiple reviews per item per user.
review = Review.objects.create(
owner=user,
item=item,
title=title,
body=body,
metadata=metadata,
visibility=visibility,
)
"""
review = Review.objects.filter(owner=user, item=item).first()
@classmethod
def review_item_by_user(
cls,
item,
user,
title,
body,
visibility=0,
created_time=None,
share_to_mastodon=False,
):
if title is None:
review = Review.objects.filter(owner=user, item=item).first()
if review is not None:
review.delete()
review = None
elif review is None:
review = Review.objects.create(owner=user, item=item, title=title, body=body, visibility=visibility)
else:
review.title = title
review.body = body
review.visibility = visibility
review.save()
"""
return None
defaults = {
"title": title,
"body": body,
"visibility": visibility,
}
if created_time:
defaults["created_time"] = (
created_time if created_time < timezone.now() else timezone.now()
)
review, created = cls.objects.update_or_create(
item=item, owner=user, defaults=defaults
)
if share_to_mastodon:
share_review(review)
return review

View file

@ -563,32 +563,29 @@ def review_edit(request, item_uuid, review_uuid=None):
else ReviewForm(request.POST)
)
if form.is_valid():
if not review:
form.instance.owner = request.user
form.instance.edited_time = timezone.now()
mark_date = None
if request.POST.get("mark_anotherday"):
mark_date = timezone.get_current_timezone().localize(
parse_datetime(request.POST.get("mark_date") + " 20:00:00")
)
if mark_date and mark_date >= timezone.now():
mark_date = None
if mark_date:
form.instance.created_time = mark_date
body = form.instance.body
if request.POST.get("leading_space"):
form.instance.body = re.sub(
body = re.sub(
r"^(\u2003*)( +)",
lambda s: "\u2003" * ((len(s[2]) + 1) // 2 + len(s[1])),
form.instance.body,
body,
flags=re.MULTILINE,
)
form.save()
if form.cleaned_data["share_to_mastodon"]:
if not share_review(form.instance):
return render_relogin(request)
return redirect(
reverse("journal:review_retrieve", args=[form.instance.uuid])
review = Review.review_item_by_user(
item,
request.user,
form.cleaned_data["title"],
body,
form.cleaned_data["visibility"],
mark_date,
form.cleaned_data["share_to_mastodon"],
)
return redirect(reverse("journal:review_retrieve", args=[review.uuid]))
else:
raise BadRequest()
else:

View file

@ -5,6 +5,7 @@ from ninja.security import django_auth
class UserSchema(Schema):
url: str
external_acct: str
display_name: str
avatar: str
@ -12,10 +13,9 @@ class UserSchema(Schema):
@api.get(
"/me",
response={200: UserSchema, 400: Result, 403: Result},
response={200: UserSchema, 401: Result},
summary="Get current user's basic info",
)
@protected_resource()
def me(request):
return 200, {
"external_acct": request.user.mastodon_username,