-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
163 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,18 @@ | ||
from django.urls import path | ||
|
||
from dynamic_raw_id.views import label_view | ||
from .views import LabelView, MultiLabelView | ||
|
||
app_name = "dynamic_raw_id" | ||
|
||
urlpatterns = [ | ||
path( | ||
"<slug:app_name>/<slug:model_name>/multiple/", | ||
label_view, | ||
{ | ||
"multi": True, | ||
"template_object_name": "objects", | ||
"template_name": "dynamic_raw_id/multi_label.html", | ||
}, | ||
MultiLabelView.as_view(), | ||
name="dynamic_raw_id_multi_label", | ||
), | ||
path( | ||
"<slug:app_name>/<slug:model_name>/", | ||
label_view, | ||
{"template_name": "dynamic_raw_id/label.html"}, | ||
LabelView.as_view(), | ||
name="dynamic_raw_id_label", | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,111 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING, Any | ||
|
||
from django.apps import apps | ||
from django.conf import settings | ||
from django.contrib.auth.decorators import user_passes_test | ||
from django.db.models.base import ModelBase | ||
from django.http import ( | ||
HttpRequest, | ||
HttpResponse, | ||
HttpResponseBadRequest, | ||
HttpResponseForbidden, | ||
HttpResponseNotFound, | ||
) | ||
from django.shortcuts import render | ||
from django.urls import reverse | ||
from django.views.generic import TemplateView | ||
|
||
if TYPE_CHECKING: | ||
from django.template import Context | ||
|
||
|
||
class LabelView(TemplateView): | ||
app_name: str | ||
model_name: str | ||
model: ModelBase | ||
template_name = "dynamic_raw_id/label.html" | ||
template_object_name: str = "object" | ||
|
||
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: | ||
self.app_name = kwargs["app_name"] | ||
self.model_name = kwargs["model_name"] | ||
|
||
# User must be authorized and at least staff level. | ||
# Intentionally raising a NotFound here to not indicate this is an API response. | ||
if not request.user.is_authenticated or not request.user.is_staff: | ||
return HttpResponseNotFound() | ||
|
||
# User must have 'view' permission of the given app_name/model_name. | ||
if not request.user.has_perm(f"{self.app_name}.view_{self.model_name}"): | ||
return HttpResponseForbidden() | ||
|
||
# The list of to obtained objects is in GET.id. | ||
# No need to resume if we didn't get it. | ||
if "id" not in request.GET: | ||
msg = "No list of objects given" | ||
return HttpResponseBadRequest(msg) | ||
|
||
# Make sure, the given app_name/model_name exists. | ||
try: | ||
self.model = apps.get_model(self.app_name, self.model_name) | ||
except LookupError: | ||
return HttpResponseBadRequest() | ||
|
||
return super().dispatch(request, *args, **kwargs) | ||
|
||
def get_template_names(self) -> list[str]: | ||
return [ | ||
f"dynamic_raw_id/{self.app_name}/{self.model_name}.html", | ||
self.template_name, | ||
] | ||
|
||
def get_obj_context(self) -> tuple[str, Any] | None: | ||
try: | ||
obj = self.model.objects.get(pk=self.request.GET["id"]) | ||
except (self.model.DoesNotExist, ValueError): | ||
return None | ||
|
||
return ( | ||
str(obj), | ||
reverse(f"admin:{self.app_name}_{self.model_name}_change", args=[obj.pk]), | ||
) | ||
|
||
def get_context_data(self, **kwargs: Any) -> Context: | ||
context = super().get_context_data(**kwargs) | ||
context.update(**{self.template_object_name: self.get_obj_context()}) | ||
return context | ||
|
||
|
||
class MultiLabelView(LabelView): | ||
""" | ||
Same as LabelView, but accepts multiple GET.id values, | ||
given as a comma separated list. | ||
""" | ||
|
||
multi: bool = False | ||
template_name = "dynamic_raw_id/multi_label.html" | ||
template_object_name: str = "objects" | ||
|
||
def get_obj_context(self) -> list[tuple[str, Any]] | None: | ||
try: | ||
object_id_list = self.model.objects.filter( | ||
pk__in=self.request.GET["id"].split(",") | ||
) | ||
except ValueError: | ||
return None | ||
|
||
objects = self.model.objects.filter(pk__in=object_id_list) | ||
return [ | ||
( | ||
str(obj), | ||
reverse( | ||
f"admin:{self.app_name}_{self.model_name}_change", args=[obj.pk] | ||
), | ||
) | ||
for obj in objects | ||
] | ||
|
||
@user_passes_test(lambda u: u.is_staff) | ||
def label_view( # noqa: PLR0913 Too Many arguments | ||
request: HttpRequest, | ||
app_name: str, | ||
model_name: str, | ||
template_name: str, | ||
multi: bool = False, | ||
template_object_name: str = "object", | ||
) -> HttpResponse: | ||
# The list of to obtained objects is in GET.id. No need to resume if we | ||
# didn't get it. | ||
if not request.GET.get("id"): | ||
msg = "No list of objects given" | ||
return HttpResponseBadRequest(settings.DEBUG and msg or "") | ||
|
||
# Given objects are either an integer or a comma-separated list of | ||
# integers. Validate them and ignore invalid values. Also strip them | ||
# in case the user entered values by hand, such as '1, 2,3'. | ||
object_list = [pk.strip() for pk in request.GET["id"].split(",")] | ||
|
||
# Make sure this model exists and the user has 'change' permission for it. | ||
# If he doesn't have this permission, Django would not display the | ||
# change_list in the popup, and the user was never able to select objects. | ||
try: | ||
model = apps.get_model(app_name, model_name) | ||
except LookupError: | ||
msg = f"Model {app_name}.{model_name} does not exist." | ||
return HttpResponseBadRequest(msg) | ||
|
||
# Check 'view' permission | ||
if not request.user.has_perm(f"{app_name}.view_{model_name}"): | ||
return HttpResponseForbidden() | ||
|
||
try: | ||
if multi: | ||
model_template = f"dynamic_raw_id/{app_name}/multi_{model_name}.html" | ||
objs = model.objects.filter(pk__in=object_list) | ||
objects = [ | ||
(obj, reverse(f"admin:{app_name}_{model_name}_change", args=[obj.pk])) | ||
for obj in objs | ||
] | ||
extra_context = {template_object_name: objects} | ||
else: | ||
model_template = f"dynamic_raw_id/{app_name}/{model_name}.html" | ||
obj = model.objects.get(pk=object_list[0]) | ||
change_url = reverse(f"admin:{app_name}_{model_name}_change", args=[obj.pk]) | ||
extra_context = {template_object_name: (obj, change_url)} | ||
|
||
# most likely, the pk wasn't convertable | ||
except ValueError: | ||
msg = "ValueError during lookup" | ||
return HttpResponseBadRequest(msg) | ||
except model.DoesNotExist: | ||
msg = "Model instance does not exist" | ||
return HttpResponseBadRequest(msg) | ||
|
||
return render(request, (model_template, template_name), extra_context) | ||
def get_template_names(self) -> list[str]: | ||
return [ | ||
f"dynamic_raw_id/{self.app_name}/multi_{self.model_name}.html", | ||
self.template_name, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters