add review api
This commit is contained in:
parent
a7857d99b2
commit
f92861105d
7 changed files with 158 additions and 64 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
125
journal/api.py
125
journal/api.py
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue