|
18 | 18 |
|
19 | 19 | # Image utils |
20 | 20 | from io import BytesIO |
21 | | -from typing import Any |
| 21 | +from typing import Any, Unpack |
22 | 22 |
|
23 | 23 | import PIL |
24 | 24 | from django.conf import settings |
25 | 25 | from django.core.files.base import ContentFile |
| 26 | +from django.db import models |
26 | 27 | from django.forms import BaseForm |
27 | | -from django.http import HttpRequest |
| 28 | +from django.http import Http404, HttpRequest |
| 29 | +from django.shortcuts import get_list_or_404 |
28 | 30 | from django.template.loader import render_to_string |
29 | 31 | from django.utils.html import SafeString |
30 | 32 | from django.utils.timezone import localdate |
@@ -188,3 +190,56 @@ def get_client_ip(request: HttpRequest) -> str | None: |
188 | 190 | return ip |
189 | 191 |
|
190 | 192 | return None |
| 193 | + |
| 194 | + |
| 195 | +Filterable = models.Model | models.QuerySet | models.Manager |
| 196 | +ListFilter = dict[str, list | tuple | set] |
| 197 | + |
| 198 | + |
| 199 | +def get_list_exact_or_404(klass: Filterable, **kwargs: Unpack[ListFilter]) -> list: |
| 200 | + """Use filter() to return a list of objects from a list of unique keys (like ids) |
| 201 | + or raises Http404 if the list has not the same length as the given one. |
| 202 | +
|
| 203 | + Work like `get_object_or_404()` but for lists of objects, with some caveats : |
| 204 | +
|
| 205 | + - The filter must be a list, a tuple or a set. |
| 206 | + - There can't be more than exactly one filter. |
| 207 | + - There must be no duplicate in the filter. |
| 208 | + - The filter should consist in unique keys (like ids), or it could fail randomly. |
| 209 | +
|
| 210 | + klass may be a Model, Manager, or QuerySet object. All other passed |
| 211 | + arguments and keyword arguments are used in the filter() query. |
| 212 | +
|
| 213 | + Raises: |
| 214 | + Http404: If the list is empty or doesn't have as many elements as the keys list. |
| 215 | + ValueError: If the first argument is not a Model, Manager, or QuerySet object. |
| 216 | + ValueError: If more than one filter is passed. |
| 217 | + TypeError: If the given filter is not a list, a tuple or a set. |
| 218 | +
|
| 219 | + Examples: |
| 220 | + Get all the products with ids 1, 2, 3: :: |
| 221 | +
|
| 222 | + products = get_list_exact_or_404(Product, id__in=[1, 2, 3]) |
| 223 | +
|
| 224 | + Don't work with duplicate ids: :: |
| 225 | +
|
| 226 | + products = get_list_exact_or_404(Product, id__in=[1, 2, 3, 3]) |
| 227 | + # Raises Http404: "The list of keys must contain no duplicates." |
| 228 | + """ |
| 229 | + if len(kwargs) > 1: |
| 230 | + raise ValueError("get_list_exact_or_404() only accepts one filter.") |
| 231 | + key, list_filter = next(iter(kwargs.items())) |
| 232 | + if not isinstance(list_filter, (list, tuple, set)): |
| 233 | + raise TypeError( |
| 234 | + f"The given filter must be a list, a tuple or a set, not {type(list_filter)}" |
| 235 | + ) |
| 236 | + if len(list_filter) != len(set(list_filter)): |
| 237 | + raise ValueError("The list of keys must contain no duplicates.") |
| 238 | + kwargs = {key: list_filter} |
| 239 | + obj_list = get_list_or_404(klass, **kwargs) |
| 240 | + if len(obj_list) != len(list_filter): |
| 241 | + raise Http404( |
| 242 | + "The given list of keys doesn't match the number of objects found." |
| 243 | + f"Expected {len(list_filter)} items, got {len(obj_list)}." |
| 244 | + ) |
| 245 | + return obj_list |
0 commit comments