replace drf by django-ninja

This commit is contained in:
thomas girod
2024-07-18 20:23:30 +02:00
parent d9531838f2
commit 3046438cb1
43 changed files with 1001 additions and 1191 deletions

29
core/api.py Normal file
View File

@ -0,0 +1,29 @@
from django.conf import settings
from django.http import HttpResponse
from ninja_extra import ControllerBase, api_controller, route
from ninja_extra.exceptions import PermissionDenied
from club.models import Mailing
from core.schemas import MarkdownSchema
from core.templatetags.renderer import markdown
@api_controller("/markdown")
class MarkdownController(ControllerBase):
@route.post("", url_name="markdown")
def render_markdown(self, body: MarkdownSchema):
"""Convert the markdown text into html."""
return HttpResponse(markdown(body.text), content_type="text/html")
@api_controller("/mailings")
class MailingListController(ControllerBase):
@route.get("", response=str)
def fetch_mailing_lists(self, key: str):
if key != settings.SITH_MAILING_FETCH_KEY:
raise PermissionDenied
mailings = Mailing.objects.filter(
is_moderated=True, club__is_active=True
).prefetch_related("subscriptions")
data = "\n".join(m.fetch_format() for m in mailings)
return data

96
core/api_permissions.py Normal file
View File

@ -0,0 +1,96 @@
"""Permission classes to be used within ninja-extra controllers.
Some permissions are global (like `IsInGroup` or `IsRoot`),
and some others are per-object (like `CanView` or `CanEdit`).
Examples:
```python
# restrict all the routes of this controller
# to subscribed users
@api_controller("/foo", permissions=[IsSubscriber])
class FooController(ControllerBase):
@route.get("/bar")
def bar_get(self):
# This route inherits the permissions of the controller
# ...
@route.bar("/bar/{bar_id}", permissions=[CanView])
def bar_get_one(self, bar_id: int):
# per-object permission resolution happens
# when calling either the `get_object_or_exception`
# or `get_object_or_none` method.
bar = self.get_object_or_exception(Counter, pk=bar_id)
# you can also call the `check_object_permission` manually
other_bar = Counter.objects.first()
self.check_object_permissions(other_bar)
# ...
# This route is restricted to counter admins and root users
@route.delete(
"/bar/{bar_id}",
permissions=[IsRoot | IsInGroup(settings.SITH_GROUP_COUNTER_ADMIN_ID)
]
def bar_delete(self, bar_id: int):
# ...
"""
from typing import Any
from django.http import HttpRequest
from ninja_extra import ControllerBase
from ninja_extra.permissions import BasePermission
class IsInGroup(BasePermission):
def __init__(self, group_pk: int):
self._group_pk = group_pk
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return request.user.is_in_group(pk=self._group_pk)
class IsRoot(BasePermission):
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return request.user.is_root
class IsSubscriber(BasePermission):
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return request.user.is_subscribed
class IsOldSubscriber(BasePermission):
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return request.user.was_subscribed
class CanView(BasePermission):
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return True
def has_object_permission(
self, request: HttpRequest, controller: ControllerBase, obj: Any
) -> bool:
return request.user.can_view(obj)
class CanEdit(BasePermission):
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return True
def has_object_permission(
self, request: HttpRequest, controller: ControllerBase, obj: Any
) -> bool:
return request.user.can_edit(obj)
class IsOwner(BasePermission):
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return True
def has_object_permission(
self, request: HttpRequest, controller: ControllerBase, obj: Any
) -> bool:
return request.user.is_owner(obj)

15
core/schemas.py Normal file
View File

@ -0,0 +1,15 @@
from ninja import ModelSchema, Schema
from core.models import User
class SimpleUserSchema(ModelSchema):
"""A schema with the minimum amount of information to represent a user."""
class Meta:
model = User
fields = ["id", "nick_name", "first_name", "last_name"]
class MarkdownSchema(Schema):
text: str

View File

@ -84,10 +84,10 @@
}
function download_pictures() {
$("#download_all_pictures").prop("disabled", true);
var xhr = new XMLHttpRequest();
const xhr = new XMLHttpRequest();
$.ajax({
type: "GET",
url: "{{ url('api:all_pictures_of_user', user=object.id) }}",
url: "{{ url('api:pictures') }}?users_identified={{ object.id }}",
tryCount: 0,
xhr: function(){
return xhr;

View File

@ -65,7 +65,7 @@ class TestUserRegistration:
{"password2": "not the same as password1"},
"Les deux mots de passe ne correspondent pas.",
),
({"email": "not-an-email"}, "Saisissez une adresse e-mail valide."),
({"email": "not-an-email"}, "Saisissez une adresse de courriel valide."),
({"first_name": ""}, "Ce champ est obligatoire."),
({"last_name": ""}, "Ce champ est obligatoire."),
({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"),
@ -310,7 +310,6 @@ http://git.an
)
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
assert response.status_code == 200
print(response.content.decode())
expected = """
<p>Guy <em>bibou</em></p>
<p><a href="http://git.an">http://git.an</a></p>

View File

@ -25,6 +25,7 @@
import types
from typing import Any
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import (
ImproperlyConfigured,
PermissionDenied,
@ -234,7 +235,7 @@ class UserIsRootMixin(GenericContentPermissionMixinBuilder):
permission_function = lambda obj, user: user.is_root
class FormerSubscriberMixin(View):
class FormerSubscriberMixin(AccessMixin):
"""Check if the user was at least an old subscriber.
Raises:
@ -247,16 +248,10 @@ class FormerSubscriberMixin(View):
return super().dispatch(request, *args, **kwargs)
class UserIsLoggedMixin(View):
"""Check if the user is logged.
Raises:
PermissionDenied:
"""
class SubscriberMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if request.user.is_anonymous:
raise PermissionDenied
if not request.user.is_subscribed:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)

View File

@ -104,7 +104,7 @@ class MarkdownInput(Textarea):
"fullscreen": _("Toggle fullscreen"),
"guide": _("Markdown guide"),
}
context["markdown_api_url"] = reverse("api:api_markdown")
context["markdown_api_url"] = reverse("api:markdown")
return context

View File

@ -29,6 +29,7 @@ from smtplib import SMTPException
from django.conf import settings
from django.contrib.auth import login, views
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied, ValidationError
from django.forms import CheckboxSelectMultiple
from django.forms.models import modelform_factory
@ -50,7 +51,6 @@ from django.views.generic.dates import MonthMixin, YearMixin
from django.views.generic.edit import FormView, UpdateView
from honeypot.decorators import check_honeypot
from api.views.sas import all_pictures_of_user
from core.models import Gift, Preferences, SithFile, User
from core.views import (
CanEditMixin,
@ -58,7 +58,6 @@ from core.views import (
CanViewMixin,
QuickNotifMixin,
TabedViewMixin,
UserIsLoggedMixin,
)
from core.views.forms import (
GiftForm,
@ -68,6 +67,7 @@ from core.views.forms import (
UserProfileForm,
)
from counter.forms import StudentCardForm
from sas.models import Picture
from subscription.models import Subscription
from trombi.views import UserTrombiForm
@ -313,7 +313,11 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
kwargs = super().get_context_data(**kwargs)
kwargs["albums"] = []
kwargs["pictures"] = {}
picture_qs = all_pictures_of_user(self.object)
picture_qs = (
Picture.objects.filter(people__user_id=self.object.id)
.order_by("parent__date", "id")
.all()
)
last_album = None
for picture in picture_qs:
album = picture.parent
@ -720,7 +724,7 @@ class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
current_tab = "groups"
class UserToolsView(QuickNotifMixin, UserTabsMixin, UserIsLoggedMixin, TemplateView):
class UserToolsView(LoginRequiredMixin, QuickNotifMixin, UserTabsMixin, TemplateView):
"""Displays the logged user's tools."""
template_name = "core/user_tools.jinja"