Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plain-admin/plain/admin/cards/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ def get_current_filter(self) -> str:
def get_filters(self) -> list[str] | Enum | None:
if isinstance(self.filters, list):
# Avoid mutating the class attribute
return self.filters.copy() # type: ignore
return self.filters.copy()
else:
return self.filters
17 changes: 17 additions & 0 deletions plain-admin/plain/admin/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,23 @@ def get_view_url(cls, obj: Any = None) -> str:
else:
return reverse(f"{_URL_NAMESPACE}:" + cls.view_name())

# Wired up by AdminViewset.get_views() to cross-link the List/Create/Detail/
# Update/Delete views registered together on a viewset.
def get_list_url(self) -> str:
return ""

def get_create_url(self) -> str:
return ""

def get_detail_url(self, obj: Any) -> str:
return ""

def get_update_url(self, obj: Any) -> str:
return ""

def get_delete_url(self, obj: Any) -> str:
return ""

def get_links(self) -> dict[str, str]:
return self.links.copy()

Expand Down
75 changes: 0 additions & 75 deletions plain-admin/plain/admin/views/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,21 +248,6 @@ def get_filter_names(self) -> list[str]:
def get_object_id(self, obj: Any) -> Any:
return self.get_field_value(obj, "id")

def get_list_url(self) -> str:
return ""

def get_create_url(self) -> str:
return ""

def get_detail_url(self, obj: Any) -> str:
return ""

def get_update_url(self, obj: Any) -> str:
return ""

def get_delete_url(self, obj: Any) -> str:
return ""

def get_object_url(self, obj: Any) -> str:
if url := self.get_detail_url(obj):
return url
Expand Down Expand Up @@ -296,21 +281,6 @@ class AdminCreateView(AdminView, CreateView):
template_name = None
nav_section = None

def get_list_url(self) -> str:
return ""

def get_create_url(self) -> str:
return ""

def get_detail_url(self, obj: Any) -> str:
return ""

def get_update_url(self, obj: Any) -> str:
return ""

def get_delete_url(self, obj: Any) -> str:
return ""

def get_success_url(self, form: "BaseForm") -> str:
if list_url := self.get_list_url():
return list_url
Expand All @@ -337,21 +307,6 @@ def get_template_names(self) -> list[str]:
"admin/detail.html", # A generic detail view for rendering any object
]

def get_list_url(self) -> str:
return ""

def get_create_url(self) -> str:
return ""

def get_detail_url(self, obj: Any) -> str:
return ""

def get_update_url(self, obj: Any) -> str:
return ""

def get_delete_url(self, obj: Any) -> str:
return ""

def get_fields(self) -> list[str]:
return self.fields.copy() # Avoid mutating the class attribute itself

Expand All @@ -374,21 +329,6 @@ class AdminUpdateView(AdminView, UpdateView):
template_name = None
nav_section = None

def get_list_url(self) -> str:
return ""

def get_create_url(self) -> str:
return ""

def get_detail_url(self, obj: Any) -> str:
return ""

def get_update_url(self, obj: Any) -> str:
return ""

def get_delete_url(self, obj: Any) -> str:
return ""

def get_links(self) -> dict[str, str]:
links = super().get_links()

Expand Down Expand Up @@ -420,21 +360,6 @@ class AdminDeleteView(AdminView, DeleteView):
template_name = "admin/delete.html"
nav_section = None

def get_list_url(self) -> str:
return ""

def get_create_url(self) -> str:
return ""

def get_detail_url(self, obj: Any) -> str:
return ""

def get_update_url(self, obj: Any) -> str:
return ""

def get_delete_url(self, obj: Any) -> str:
return ""

def get_links(self) -> dict[str, str]:
links = super().get_links()

Expand Down
22 changes: 12 additions & 10 deletions plain-admin/plain/admin/views/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from plain.views import View
from .base import AdminView


class AdminViewset:
@classmethod
def get_views(cls) -> list[type[View]]:
def get_views(cls) -> list[type[AdminView]]:
"""Views are defined as inner classes on the viewset class."""

# Primary views that we can interlink automatically
Expand All @@ -30,26 +30,28 @@ def get_views(cls) -> list[type[View]]:
views = []

for attr in cls.__dict__.values():
if isinstance(attr, type) and issubclass(attr, View):
if isinstance(attr, type) and issubclass(attr, AdminView):
views.append(attr)

for view in views:
# Dynamic attributes stamped onto the view class by the viewset.
view.viewset = cls # ty: ignore[unresolved-attribute]
view.viewset = cls

# Wire up the classmethod that generates each sibling view's own
# URL as this view's getter, so List/Create/Detail/Update/Delete
# views registered together on a viewset can cross-link.
if ListView:
view.get_list_url = ListView.get_view_url # ty: ignore[unresolved-attribute]
view.get_list_url = ListView.get_view_url

if CreateView:
view.get_create_url = CreateView.get_view_url # ty: ignore[unresolved-attribute]
view.get_create_url = CreateView.get_view_url

if DetailView:
view.get_detail_url = DetailView.get_view_url # ty: ignore[unresolved-attribute]
view.get_detail_url = DetailView.get_view_url

if UpdateView:
view.get_update_url = UpdateView.get_view_url # ty: ignore[unresolved-attribute]
view.get_update_url = UpdateView.get_view_url

if DeleteView:
view.get_delete_url = DeleteView.get_view_url # ty: ignore[unresolved-attribute]
view.get_delete_url = DeleteView.get_view_url

return views
26 changes: 16 additions & 10 deletions plain-auth/plain/auth/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from http.cookies import SimpleCookie
from typing import TYPE_CHECKING, Any

from plain.http import Response
from plain.http.request import Request
from plain.runtime import settings
from plain.sessions import SessionStore
Expand All @@ -26,16 +27,21 @@ def login_client(client: Client, user: Any) -> None:
login(request, user)
session = get_request_session(request)
session.save()
session_cookie = settings.SESSION_COOKIE_NAME
client.cookies[session_cookie] = session.session_key
cookie_data = {
"max-age": None,
"path": "/",
"domain": settings.SESSION_COOKIE_DOMAIN,
"secure": settings.SESSION_COOKIE_SECURE or None,
"expires": None,
}
client.cookies[session_cookie].update(cookie_data)
assert session.session_key is not None, "Session key should exist after save()"

# Build the same Set-Cookie a real response would send, matching
# plain.sessions' SessionMiddleware, then copy it onto the test client.
response = Response()
response.set_cookie(
settings.SESSION_COOKIE_NAME,
session.session_key,
domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=bool(settings.SESSION_COOKIE_SECURE),
httponly=bool(settings.SESSION_COOKIE_HTTPONLY),
samesite=settings.SESSION_COOKIE_SAMESITE,
)
client.cookies.update(response.cookies)


def logout_client(client: Client) -> None:
Expand Down
53 changes: 7 additions & 46 deletions plain-postgres/plain/postgres/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from collections import defaultdict
from collections.abc import Iterable
from functools import cached_property
from typing import TYPE_CHECKING, Any, Literal, overload
from typing import TYPE_CHECKING, Any

from plain.postgres.exceptions import FieldDoesNotExist
from plain.postgres.query import QuerySet
Expand Down Expand Up @@ -175,7 +175,7 @@ def add_field(self, field: Field) -> None:
self._expire_cache(reverse=False)

@cached_property
def fields(self) -> ImmutableList[Field]:
def fields(self) -> ImmutableList:
from plain.postgres.fields.related import RelatedField

"""
Expand Down Expand Up @@ -213,7 +213,7 @@ def is_not_a_generic_relation(f: Any) -> bool:
)

@cached_property
def concrete_fields(self) -> ImmutableList[Field]:
def concrete_fields(self) -> ImmutableList:
"""
Return a list of all concrete fields on the model and its parents.

Expand All @@ -226,7 +226,7 @@ def concrete_fields(self) -> ImmutableList[Field]:
)

@cached_property
def local_concrete_fields(self) -> ImmutableList[Field]:
def local_concrete_fields(self) -> ImmutableList:
"""
Return a list of all concrete fields on the model.

Expand All @@ -239,7 +239,7 @@ def local_concrete_fields(self) -> ImmutableList[Field]:
)

@cached_property
def many_to_many(self) -> ImmutableList[Field]:
def many_to_many(self) -> ImmutableList:
"""
Return a list of all many to many fields on the model and its parents.

Expand Down Expand Up @@ -407,19 +407,7 @@ def _expire_cache(self, forward: bool = True, reverse: bool = True) -> None:
delattr(self, cache_key)
self._get_fields_cache = {}

@overload
def get_fields(
self, include_reverse: Literal[False] = False
) -> ImmutableList[Field]: ...

@overload
def get_fields(
self, include_reverse: Literal[True]
) -> ImmutableList[Field | ForeignObjectRel]: ...

def get_fields(
self, include_reverse: bool = False
) -> ImmutableList[Field | ForeignObjectRel]:
def get_fields(self, include_reverse: bool = False) -> ImmutableList:
"""
Return a list of fields associated to the model.

Expand All @@ -434,40 +422,13 @@ def get_fields(
"""
return self._get_fields(reverse=include_reverse)

@overload
def _get_fields(
self,
*,
forward: Literal[True] = True,
reverse: Literal[False],
seen_models: set[type[Any]] | None = None,
) -> ImmutableList[Field]: ...

@overload
def _get_fields(
self,
*,
forward: Literal[False],
reverse: Literal[True] = True,
seen_models: set[type[Any]] | None = None,
) -> ImmutableList[ForeignObjectRel]: ...

@overload
def _get_fields(
self,
*,
forward: bool = True,
reverse: bool = True,
seen_models: set[type[Any]] | None = None,
) -> ImmutableList[Field | ForeignObjectRel]: ...

def _get_fields(
self,
*,
forward: bool = True,
reverse: bool = True,
seen_models: set[type[Any]] | None = None,
) -> ImmutableList[Field | ForeignObjectRel]:
) -> ImmutableList:
"""
Internal helper function to return fields of the model.

Expand Down
2 changes: 1 addition & 1 deletion plain-postgres/plain/postgres/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ def bulk_create(
update_fields_objs,
unique_fields_objs,
)
fields = meta.concrete_fields
fields = list(meta.concrete_fields)
self._prepare_for_bulk_create(objs)
with transaction.atomic(savepoint=False):
objs_with_id, objs_without_id = partition(lambda o: o.id is None, objs)
Expand Down
6 changes: 3 additions & 3 deletions plain/plain/test/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def __init__(
) -> None:
self.json_encoder = json_encoder
self._default_headers: dict[str, str] = headers or {}
self.cookies: SimpleCookie[str] = SimpleCookie()
self.cookies: SimpleCookie = SimpleCookie()

def _build_request(
self,
Expand Down Expand Up @@ -558,12 +558,12 @@ def __init__(
self.raise_request_exception = raise_request_exception

@property
def cookies(self) -> SimpleCookie[str]:
def cookies(self) -> SimpleCookie:
"""Access the cookies from the request factory."""
return self._request_factory.cookies

@cookies.setter
def cookies(self, value: SimpleCookie[str]) -> None:
def cookies(self, value: SimpleCookie) -> None:
"""Set the cookies on the request factory."""
self._request_factory.cookies = value

Expand Down
4 changes: 2 additions & 2 deletions plain/plain/utils/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def dict(self) -> builtins.dict[str, Any]:
return {key: self[key] for key in self}


class ImmutableList(tuple):
class ImmutableList[T](tuple[T, ...]):
"""
A tuple-like object that raises useful errors when it is asked to mutate.

Expand All @@ -254,7 +254,7 @@ def __new__(
*args: Any,
warning: str = "ImmutableList object is immutable.",
**kwargs: Any,
) -> ImmutableList:
) -> ImmutableList[T]:
self = tuple.__new__(cls, *args, **kwargs)
self.warning = warning
return self
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dev = [
"plain-tunnel",
"plain-vendor",
# Type checking and better dev experience
"ty>=0.0.65",
"ty>=0.0.69",
"psycopg[binary]>=3.2.12",
]

Expand Down
Loading
Loading