API to moderate and delete news

This commit is contained in:
imperosol 2025-01-20 17:03:25 +01:00
parent 18967cf3d6
commit 74385bde04
2 changed files with 85 additions and 1 deletions

View File

@ -5,6 +5,8 @@ from django.http import Http404
from ninja_extra import ControllerBase, api_controller, route from ninja_extra import ControllerBase, api_controller, route
from com.calendar import IcsCalendar from com.calendar import IcsCalendar
from com.models import News
from core.auth.api_permissions import HasPerm
from core.views.files import send_raw_file from core.views.files import send_raw_file
@ -17,7 +19,7 @@ class CalendarController(ControllerBase):
"""Return the ICS file of the AE Google Calendar """Return the ICS file of the AE Google Calendar
Because of Google's cors rules, we can't just do a request to google ics Because of Google's cors rules, we can't just do a request to google ics
from the frontend. Google is blocking CORS request in it's responses headers. from the frontend. Google is blocking CORS request in its responses headers.
The only way to do it from the frontend is to use Google Calendar API with an API key The only way to do it from the frontend is to use Google Calendar API with an API key
This is not especially desirable as your API key is going to be provided to the frontend. This is not especially desirable as your API key is going to be provided to the frontend.
@ -30,3 +32,26 @@ class CalendarController(ControllerBase):
@route.get("/internal.ics", url_name="calendar_internal") @route.get("/internal.ics", url_name="calendar_internal")
def calendar_internal(self): def calendar_internal(self):
return send_raw_file(IcsCalendar.get_internal()) return send_raw_file(IcsCalendar.get_internal())
@api_controller("/news")
class NewsController(ControllerBase):
@route.patch(
"/{news_id}/moderate",
permissions=[HasPerm("com.moderate_news")],
url_name="moderate_news",
)
def moderate_news(self, news_id: int):
news = self.get_object_or_exception(News, id=news_id)
if not news.is_moderated:
news.is_moderated = True
news.save()
@route.delete(
"/{news_id}",
permissions=[HasPerm("com.delete_news")],
url_name="delete_news",
)
def delete_news(self, news_id: int):
news = self.get_object_or_exception(News, id=news_id)
news.delete()

View File

@ -6,12 +6,16 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission
from django.http import HttpResponse from django.http import HttpResponse
from django.test.client import Client from django.test.client import Client
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from model_bakery import baker
from com.calendar import IcsCalendar from com.calendar import IcsCalendar
from com.models import News
from core.models import User
@dataclass @dataclass
@ -120,3 +124,58 @@ class TestInternalCalendar:
out_file = accel_redirect_to_file(response) out_file = accel_redirect_to_file(response)
assert out_file is not None assert out_file is not None
assert out_file.exists() assert out_file.exists()
@pytest.mark.django_db
class TestModerateNews:
@pytest.mark.parametrize("news_is_moderated", [True, False])
def test_moderation_ok(self, client: Client, news_is_moderated: bool): # noqa FBT
user = baker.make(
User, user_permissions=[Permission.objects.get(codename="moderate_news")]
)
# The API call should work even if the news is initially moderated.
# In the latter case, the result should be a noop, rather than an error.
news = baker.make(News, is_moderated=news_is_moderated)
client.force_login(user)
response = client.patch(
reverse("api:moderate_news", kwargs={"news_id": news.id})
)
assert response.status_code == 200
news.refresh_from_db()
assert news.is_moderated
def test_moderation_forbidden(self, client: Client):
user = baker.make(User)
news = baker.make(News, is_moderated=False)
client.force_login(user)
response = client.patch(
reverse("api:moderate_news", kwargs={"news_id": news.id})
)
assert response.status_code == 403
news.refresh_from_db()
assert not news.is_moderated
@pytest.mark.django_db
class TestDeleteNews:
def test_delete_news_ok(self, client: Client):
user = baker.make(
User, user_permissions=[Permission.objects.get(codename="delete_news")]
)
news = baker.make(News)
client.force_login(user)
response = client.delete(
reverse("api:delete_news", kwargs={"news_id": news.id})
)
assert response.status_code == 200
assert not News.objects.filter(id=news.id).exists()
def test_delete_news_forbidden(self, client: Client):
user = baker.make(User)
news = baker.make(News)
client.force_login(user)
response = client.delete(
reverse("api:delete_news", kwargs={"news_id": news.id})
)
assert response.status_code == 403
assert News.objects.filter(id=news.id).exists()