Improve hashtag case handling and implement /api/v1/tags/<hashtag> endpoint (#554)

* Lowercase hashtag before loading its timeline

* Implement /api/v1/tags/<hashtag> endpoint

* Lower hashtag before un-/following

* Fix field name for hashtag following/followed boolean
This commit is contained in:
Christof Dorner 2023-04-06 21:14:21 +00:00 committed by GitHub
parent 216915ddb8
commit b31c5156ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 12 deletions

View file

@ -168,14 +168,14 @@ class Hashtag(StatorModel):
results[date(year, month, day)] = val results[date(year, month, day)] = val
return dict(sorted(results.items(), reverse=True)[:num]) return dict(sorted(results.items(), reverse=True)[:num])
def to_mastodon_json(self, followed: bool | None = None): def to_mastodon_json(self, following: bool | None = None):
value = { value = {
"name": self.hashtag, "name": self.hashtag,
"url": self.urls.view.full(), # type: ignore "url": self.urls.view.full(), # type: ignore
"history": [], "history": [],
} }
if followed is not None: if following is not None:
value["followed"] = followed value["following"] = following
return value return value

View file

@ -276,15 +276,15 @@ class Tag(Schema):
name: str name: str
url: str url: str
history: dict history: dict
followed: bool | None following: bool | None
@classmethod @classmethod
def from_hashtag( def from_hashtag(
cls, cls,
hashtag: activities_models.Hashtag, hashtag: activities_models.Hashtag,
followed: bool | None = None, following: bool | None = None,
) -> "Tag": ) -> "Tag":
return cls(**hashtag.to_mastodon_json(followed=followed)) return cls(**hashtag.to_mastodon_json(following=following))
class FollowedTag(Tag): class FollowedTag(Tag):
@ -295,7 +295,7 @@ class FollowedTag(Tag):
cls, cls,
follow: users_models.HashtagFollow, follow: users_models.HashtagFollow,
) -> "FollowedTag": ) -> "FollowedTag":
return cls(id=follow.id, **follow.hashtag.to_mastodon_json(followed=True)) return cls(id=follow.id, **follow.hashtag.to_mastodon_json(following=True))
@classmethod @classmethod
def map_from_follows( def map_from_follows(

View file

@ -96,6 +96,7 @@ urlpatterns = [
path("v1/statuses/<id>/unbookmark", statuses.unbookmark_status), path("v1/statuses/<id>/unbookmark", statuses.unbookmark_status),
# Tags # Tags
path("v1/followed_tags", tags.followed_tags), path("v1/followed_tags", tags.followed_tags),
path("v1/tags/<hashtag>", tags.hashtag),
path("v1/tags/<id>/follow", tags.follow), path("v1/tags/<id>/follow", tags.follow),
path("v1/tags/<id>/unfollow", tags.unfollow), path("v1/tags/<id>/unfollow", tags.unfollow),
# Timelines # Timelines

View file

@ -9,6 +9,22 @@ from api.pagination import MastodonPaginator, PaginatingApiResponse, PaginationR
from users.models import HashtagFollow from users.models import HashtagFollow
@api_view.get
def hashtag(request: HttpRequest, hashtag: str) -> schemas.Tag:
tag = get_object_or_404(
Hashtag,
pk=hashtag.lower(),
)
following = None
if request.identity:
following = tag.followers.filter(identity=request.identity).exists()
return schemas.Tag.from_hashtag(
tag,
following=following,
)
@scope_required("read:follows") @scope_required("read:follows")
@api_view.get @api_view.get
def followed_tags( def followed_tags(
@ -42,12 +58,12 @@ def follow(
) -> schemas.Tag: ) -> schemas.Tag:
hashtag = get_object_or_404( hashtag = get_object_or_404(
Hashtag, Hashtag,
pk=id, pk=id.lower(),
) )
request.identity.hashtag_follows.get_or_create(hashtag=hashtag) request.identity.hashtag_follows.get_or_create(hashtag=hashtag)
return schemas.Tag.from_hashtag( return schemas.Tag.from_hashtag(
hashtag, hashtag,
followed=True, following=True,
) )
@ -59,10 +75,10 @@ def unfollow(
) -> schemas.Tag: ) -> schemas.Tag:
hashtag = get_object_or_404( hashtag = get_object_or_404(
Hashtag, Hashtag,
pk=id, pk=id.lower(),
) )
request.identity.hashtag_follows.filter(hashtag=hashtag).delete() request.identity.hashtag_follows.filter(hashtag=hashtag).delete()
return schemas.Tag.from_hashtag( return schemas.Tag.from_hashtag(
hashtag, hashtag,
followed=False, following=False,
) )

View file

@ -101,7 +101,7 @@ def hashtag(
) -> ApiResponse[list[schemas.Status]]: ) -> ApiResponse[list[schemas.Status]]:
if limit > 40: if limit > 40:
limit = 40 limit = 40
queryset = TimelineService(request.identity).hashtag(hashtag) queryset = TimelineService(request.identity).hashtag(hashtag.lower())
if local: if local:
queryset = queryset.filter(local=True) queryset = queryset.filter(local=True)
if only_media: if only_media: