Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-django-guardian for
openSUSE:Factory checked in at 2026-08-28 19:55:07
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-guardian (Old)
and /work/SRC/openSUSE:Factory/.python-django-guardian.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-guardian"
Fri Aug 28 19:55:07 2026 rev:25 rq:1374381 version:3.3.4
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-guardian/python-django-guardian.changes
2026-05-04 12:57:58.752498363 +0200
+++
/work/SRC/openSUSE:Factory/.python-django-guardian.new.1265/python-django-guardian.changes
2026-08-28 19:58:05.837449648 +0200
@@ -1,0 +2,15 @@
+Fri Aug 28 06:03:24 UTC 2026 - Andreas Schneider <[email protected]>
+
+- update to version 3.3.4:
+ documentation is refactored, ai rules are added
+- update to version 3.3.3:
+ * Update configuration.md
+ * Fix generic object_pk bigint cast regression in shortcuts
+- update to version 3.3.2:
+ * Add note box regarding Meta inheritance
+ * Fixed user and group inconsistency with get perms
+ * Documentation: fix readme link to docs, add paperless-ngx to usage
+ * Move common code from shortcuts into dedicated methods
+ * Remove a query in prefetch_perms()
+
+-------------------------------------------------------------------
Old:
----
django_guardian-3.3.1.tar.gz
New:
----
django_guardian-3.3.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-guardian.spec ++++++
--- /var/tmp/diff_new_pack.cqbX8S/_old 2026-08-28 19:58:06.808483597 +0200
+++ /var/tmp/diff_new_pack.cqbX8S/_new 2026-08-28 19:58:06.810483667 +0200
@@ -19,7 +19,7 @@
%{?sle15_python_module_pythons}
%define pypi_package_name django-guardian
Name: python-%{pypi_package_name}
-Version: 3.3.1
+Version: 3.3.4
Release: 0
Summary: Implementation of per object permissions for Django
License: BSD-2-Clause
@@ -34,7 +34,10 @@
BuildRequires: %{python_module wheel}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
+# SECTION test requirements
Requires: python-Django >= 3.2
+# /SECTION
+BuildRequires: fdupes
Requires: python-typing-extensions >= 4.12.0
BuildArch: noarch
%python_subpackages
++++++ django_guardian-3.3.1.tar.gz -> django_guardian-3.3.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_guardian-3.3.1/PKG-INFO
new/django_guardian-3.3.4/PKG-INFO
--- old/django_guardian-3.3.1/PKG-INFO 2026-03-30 11:44:43.758969800 +0200
+++ new/django_guardian-3.3.4/PKG-INFO 2026-08-27 02:04:41.423302400 +0200
@@ -1,16 +1,15 @@
Metadata-Version: 2.4
Name: django-guardian
-Version: 3.3.1
+Version: 3.3.4
Summary: Per object permissions for Django
-Author: Chris Maggiuli, Bona Fide IT GmbH, Columbia University, SIS
Development and Application Services
-Author-email: Lukasz Balcerzak <[email protected]>, Cesar Canassa
<[email protected]>, Vincent Driessen <[email protected]>, John Hensley
<[email protected]>, Ramanan Sivaranjan <[email protected]>,
Woosuk Suh <[email protected]>, Bojan Mihelac <[email protected]>, Rafael
Ponieman <[email protected]>, Daniel Sokolowski <[email protected]>,
Ali Lozano <[email protected]>, BJ Dierkes <[email protected]>, Rach
Belaid <[email protected]>, Michael Crosby <[email protected]>,
Greg Hinch <[email protected]>, Aram Dulyan <[email protected]>, Florian Hahn
<[email protected]>, Piotr Kilczuk <[email protected]>, Reavis Sutphin-Gray
<[email protected]>, Adrián López <[email protected]>, Jameel
Al-Aziz <[email protected]>, "John P. Neumann" <[email protected]>, Andreas
Madsack <[email protected]>, Ivan Kharlamov <[email protected]>, Miguel
de Val-Borro <[email protected]>, Jan Nakladal <[email protected]>,
Yonel Ceruto <[email protected]>, Luke Faraone <[email protected]>, John
Wegis <[email protected]>, Florentin Sardan
<[email protected]>, Geoff Greer <[email protected]>, Hans Larsen
<[email protected]>, "Fabio C. Barrionuevo da Luz" <[email protected]>, Tomasz Wsuł
<[email protected]>, Xavier Ordoquy <[email protected]>, Joshua Bonnett
<[email protected]>, Jernej Kos <[email protected]>, Bruno Ribeiro da Silva
<[email protected]>, Cezar Jenkins <[email protected]>, Warren Volz
<[email protected]>, Omer Katz <[email protected]>, Vishal Lal
<[email protected]>, Steven DeMartini <[email protected]>, zauddelig
<[email protected]>, Remco Wendt <[email protected]>, Kevin London
<[email protected]>, Kouhei Maeda <[email protected]>, Samuel Sutch
<[email protected]>, Morgan Aubert <[email protected]>, Brian May
<[email protected]>, Troy Grosfield <[email protected]>,
Michael Drescher <[email protected]>, Verena Jaspersen <verena.
[email protected]>, Bertrand Svetchine <[email protected]>, Frank
Wickström <[email protected]>, George Karakostas <[email protected]>,
Adam Dobrawy <[email protected]>, Jeff Hackshaw <[email protected]>,
Chase Bennett <[email protected]>, Jonny Arnold <[email protected]>, Davis
Raymond Muro <[email protected]>, Richard de Wit <[email protected]>,
Pedro Rojas Gavidia <[email protected]>, Rainshaw Gao
<[email protected]>, Tom Clark <[email protected]>, Meredith Hoo
<[email protected]>, Cornelius Hoffmann <[email protected]>, Sezer BOZKIR
<[email protected]>
-Maintainer: Michael K., Chris Maggiuli, Bona Fide IT GmbH
-Maintainer-email: Tom Clark <[email protected]>, Brian May
<[email protected]>, Lukasz Balcerzak <[email protected]>,
Adam Dobrawy <[email protected]>, David Graham <[email protected]>,
Sezer BOZKIR <[email protected]>
+Author: Chris Maggiuli, Bona Fide IT GmbH, Columbia University, SIS
Development and Application Services, Michael K., Chris Maggiuli, Bona Fide IT
GmbH
+Author-email: Lukasz Balcerzak <[email protected]>, Cesar Canassa
<[email protected]>, Vincent Driessen <[email protected]>, John Hensley
<[email protected]>, Ramanan Sivaranjan <[email protected]>,
Woosuk Suh <[email protected]>, Bojan Mihelac <[email protected]>, Rafael
Ponieman <[email protected]>, Daniel Sokolowski <[email protected]>,
Ali Lozano <[email protected]>, BJ Dierkes <[email protected]>, Rach
Belaid <[email protected]>, Michael Crosby <[email protected]>,
Greg Hinch <[email protected]>, Aram Dulyan <[email protected]>, Florian Hahn
<[email protected]>, Piotr Kilczuk <[email protected]>, Reavis Sutphin-Gray
<[email protected]>, Adrián López <[email protected]>, Jameel
Al-Aziz <[email protected]>, "John P. Neumann" <[email protected]>, Andreas
Madsack <[email protected]>, Ivan Kharlamov <[email protected]>, Miguel
de Val-Borro <[email protected]>, Jan Nakladal <[email protected]>,
Yonel Ceruto <[email protected]>, Luke Faraone <[email protected]>, John
Wegis <[email protected]>, Florentin Sardan
<[email protected]>, Geoff Greer <[email protected]>, Hans Larsen
<[email protected]>, "Fabio C. Barrionuevo da Luz" <[email protected]>, Tomasz Wsuł
<[email protected]>, Xavier Ordoquy <[email protected]>, Joshua Bonnett
<[email protected]>, Jernej Kos <[email protected]>, Bruno Ribeiro da Silva
<[email protected]>, Cezar Jenkins <[email protected]>, Warren Volz
<[email protected]>, Omer Katz <[email protected]>, Vishal Lal
<[email protected]>, Steven DeMartini <[email protected]>, zauddelig
<[email protected]>, Remco Wendt <[email protected]>, Kevin London
<[email protected]>, Kouhei Maeda <[email protected]>, Samuel Sutch
<[email protected]>, Morgan Aubert <[email protected]>, Brian May
<[email protected]>, Troy Grosfield <[email protected]>,
Michael Drescher <[email protected]>, Verena Jaspersen <verena.
[email protected]>, Bertrand Svetchine <[email protected]>, Frank
Wickström <[email protected]>, George Karakostas <[email protected]>,
Adam Dobrawy <[email protected]>, Jeff Hackshaw <[email protected]>,
Chase Bennett <[email protected]>, Jonny Arnold <[email protected]>, Davis
Raymond Muro <[email protected]>, Richard de Wit <[email protected]>,
Pedro Rojas Gavidia <[email protected]>, Rainshaw Gao
<[email protected]>, Tom Clark <[email protected]>, David Graham
<[email protected]>, Meredith Hoo <[email protected]>, Cornelius
Hoffmann <[email protected]>, Sezer BOZKIR <[email protected]>, Paul Martin
<[email protected]>, marko1olo <[email protected]>
+Maintainer-email: Sezer BOZKIR <[email protected]>, David Graham
<[email protected]>, Tom Clark <[email protected]>
License: BSD-2-Clause
Project-URL: Homepage, https://github.com/django-guardian/django-guardian
Project-URL: Documentation, https://django-guardian.readthedocs.io/
Project-URL: Repository, https://github.com/django-guardian/django-guardian
-Project-URL: Issues, https://github.com/me/spam/issues
+Project-URL: Issues, https://github.com/django-guardian/django-guardian/issues
Project-URL: Changelog,
https://github.com/django-guardian/django-guardian/releases
Keywords: django,permissions,authorization,object,row,level
Classifier: Development Status :: 5 - Production/Stable
@@ -56,7 +55,13 @@
Online documentation is available at
[https://django-guardian.readthedocs.io/](https://django-guardian.readthedocs.io/).
-**See real-world usage**: Check out [Who Uses
Guardian](https://django-guardian.readthedocs.io/en/latest/who-uses-guardian/)
to see how thousands of projects worldwide use django-guardian in production.
+**See real-world usage**: Check out [Who Uses
Guardian](https://django-guardian.readthedocs.io/en/stable/who-uses-guardian/)
to see how thousands of projects worldwide use django-guardian in production.
+
+## Contributing
+
+Please read
[`CONTRIBUTING.md`](https://github.com/django-guardian/django-guardian/blob/main/CONTRIBUTING.md)
before opening a pull request. It documents the repository's issue-first
workflow, branch targeting rules, local `uv` setup, required test and lint
commands, documentation expectations, and AI/LLM contribution policy.
+
+If you need to report a security issue, do **not** open a public issue. Follow
[`SECURITY.md`](https://github.com/django-guardian/django-guardian/blob/main/SECURITY.md)
and use the repository's [private vulnerability reporting
flow](https://github.com/django-guardian/django-guardian/security).
## Installation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_guardian-3.3.1/README.md
new/django_guardian-3.3.4/README.md
--- old/django_guardian-3.3.1/README.md 2026-03-30 11:44:38.000000000 +0200
+++ new/django_guardian-3.3.4/README.md 2026-08-27 02:04:37.286000300 +0200
@@ -13,7 +13,13 @@
Online documentation is available at
[https://django-guardian.readthedocs.io/](https://django-guardian.readthedocs.io/).
-**See real-world usage**: Check out [Who Uses
Guardian](https://django-guardian.readthedocs.io/en/latest/who-uses-guardian/)
to see how thousands of projects worldwide use django-guardian in production.
+**See real-world usage**: Check out [Who Uses
Guardian](https://django-guardian.readthedocs.io/en/stable/who-uses-guardian/)
to see how thousands of projects worldwide use django-guardian in production.
+
+## Contributing
+
+Please read
[`CONTRIBUTING.md`](https://github.com/django-guardian/django-guardian/blob/main/CONTRIBUTING.md)
before opening a pull request. It documents the repository's issue-first
workflow, branch targeting rules, local `uv` setup, required test and lint
commands, documentation expectations, and AI/LLM contribution policy.
+
+If you need to report a security issue, do **not** open a public issue. Follow
[`SECURITY.md`](https://github.com/django-guardian/django-guardian/blob/main/SECURITY.md)
and use the repository's [private vulnerability reporting
flow](https://github.com/django-guardian/django-guardian/security).
## Installation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_guardian-3.3.1/django_guardian.egg-info/PKG-INFO
new/django_guardian-3.3.4/django_guardian.egg-info/PKG-INFO
--- old/django_guardian-3.3.1/django_guardian.egg-info/PKG-INFO 2026-03-30
11:44:43.000000000 +0200
+++ new/django_guardian-3.3.4/django_guardian.egg-info/PKG-INFO 2026-08-27
02:04:41.379073900 +0200
@@ -1,16 +1,15 @@
Metadata-Version: 2.4
Name: django-guardian
-Version: 3.3.1
+Version: 3.3.4
Summary: Per object permissions for Django
-Author: Chris Maggiuli, Bona Fide IT GmbH, Columbia University, SIS
Development and Application Services
-Author-email: Lukasz Balcerzak <[email protected]>, Cesar Canassa
<[email protected]>, Vincent Driessen <[email protected]>, John Hensley
<[email protected]>, Ramanan Sivaranjan <[email protected]>,
Woosuk Suh <[email protected]>, Bojan Mihelac <[email protected]>, Rafael
Ponieman <[email protected]>, Daniel Sokolowski <[email protected]>,
Ali Lozano <[email protected]>, BJ Dierkes <[email protected]>, Rach
Belaid <[email protected]>, Michael Crosby <[email protected]>,
Greg Hinch <[email protected]>, Aram Dulyan <[email protected]>, Florian Hahn
<[email protected]>, Piotr Kilczuk <[email protected]>, Reavis Sutphin-Gray
<[email protected]>, Adrián López <[email protected]>, Jameel
Al-Aziz <[email protected]>, "John P. Neumann" <[email protected]>, Andreas
Madsack <[email protected]>, Ivan Kharlamov <[email protected]>, Miguel
de Val-Borro <[email protected]>, Jan Nakladal <[email protected]>,
Yonel Ceruto <[email protected]>, Luke Faraone <[email protected]>, John
Wegis <[email protected]>, Florentin Sardan
<[email protected]>, Geoff Greer <[email protected]>, Hans Larsen
<[email protected]>, "Fabio C. Barrionuevo da Luz" <[email protected]>, Tomasz Wsuł
<[email protected]>, Xavier Ordoquy <[email protected]>, Joshua Bonnett
<[email protected]>, Jernej Kos <[email protected]>, Bruno Ribeiro da Silva
<[email protected]>, Cezar Jenkins <[email protected]>, Warren Volz
<[email protected]>, Omer Katz <[email protected]>, Vishal Lal
<[email protected]>, Steven DeMartini <[email protected]>, zauddelig
<[email protected]>, Remco Wendt <[email protected]>, Kevin London
<[email protected]>, Kouhei Maeda <[email protected]>, Samuel Sutch
<[email protected]>, Morgan Aubert <[email protected]>, Brian May
<[email protected]>, Troy Grosfield <[email protected]>,
Michael Drescher <[email protected]>, Verena Jaspersen <verena.
[email protected]>, Bertrand Svetchine <[email protected]>, Frank
Wickström <[email protected]>, George Karakostas <[email protected]>,
Adam Dobrawy <[email protected]>, Jeff Hackshaw <[email protected]>,
Chase Bennett <[email protected]>, Jonny Arnold <[email protected]>, Davis
Raymond Muro <[email protected]>, Richard de Wit <[email protected]>,
Pedro Rojas Gavidia <[email protected]>, Rainshaw Gao
<[email protected]>, Tom Clark <[email protected]>, Meredith Hoo
<[email protected]>, Cornelius Hoffmann <[email protected]>, Sezer BOZKIR
<[email protected]>
-Maintainer: Michael K., Chris Maggiuli, Bona Fide IT GmbH
-Maintainer-email: Tom Clark <[email protected]>, Brian May
<[email protected]>, Lukasz Balcerzak <[email protected]>,
Adam Dobrawy <[email protected]>, David Graham <[email protected]>,
Sezer BOZKIR <[email protected]>
+Author: Chris Maggiuli, Bona Fide IT GmbH, Columbia University, SIS
Development and Application Services, Michael K., Chris Maggiuli, Bona Fide IT
GmbH
+Author-email: Lukasz Balcerzak <[email protected]>, Cesar Canassa
<[email protected]>, Vincent Driessen <[email protected]>, John Hensley
<[email protected]>, Ramanan Sivaranjan <[email protected]>,
Woosuk Suh <[email protected]>, Bojan Mihelac <[email protected]>, Rafael
Ponieman <[email protected]>, Daniel Sokolowski <[email protected]>,
Ali Lozano <[email protected]>, BJ Dierkes <[email protected]>, Rach
Belaid <[email protected]>, Michael Crosby <[email protected]>,
Greg Hinch <[email protected]>, Aram Dulyan <[email protected]>, Florian Hahn
<[email protected]>, Piotr Kilczuk <[email protected]>, Reavis Sutphin-Gray
<[email protected]>, Adrián López <[email protected]>, Jameel
Al-Aziz <[email protected]>, "John P. Neumann" <[email protected]>, Andreas
Madsack <[email protected]>, Ivan Kharlamov <[email protected]>, Miguel
de Val-Borro <[email protected]>, Jan Nakladal <[email protected]>,
Yonel Ceruto <[email protected]>, Luke Faraone <[email protected]>, John
Wegis <[email protected]>, Florentin Sardan
<[email protected]>, Geoff Greer <[email protected]>, Hans Larsen
<[email protected]>, "Fabio C. Barrionuevo da Luz" <[email protected]>, Tomasz Wsuł
<[email protected]>, Xavier Ordoquy <[email protected]>, Joshua Bonnett
<[email protected]>, Jernej Kos <[email protected]>, Bruno Ribeiro da Silva
<[email protected]>, Cezar Jenkins <[email protected]>, Warren Volz
<[email protected]>, Omer Katz <[email protected]>, Vishal Lal
<[email protected]>, Steven DeMartini <[email protected]>, zauddelig
<[email protected]>, Remco Wendt <[email protected]>, Kevin London
<[email protected]>, Kouhei Maeda <[email protected]>, Samuel Sutch
<[email protected]>, Morgan Aubert <[email protected]>, Brian May
<[email protected]>, Troy Grosfield <[email protected]>,
Michael Drescher <[email protected]>, Verena Jaspersen <verena.
[email protected]>, Bertrand Svetchine <[email protected]>, Frank
Wickström <[email protected]>, George Karakostas <[email protected]>,
Adam Dobrawy <[email protected]>, Jeff Hackshaw <[email protected]>,
Chase Bennett <[email protected]>, Jonny Arnold <[email protected]>, Davis
Raymond Muro <[email protected]>, Richard de Wit <[email protected]>,
Pedro Rojas Gavidia <[email protected]>, Rainshaw Gao
<[email protected]>, Tom Clark <[email protected]>, David Graham
<[email protected]>, Meredith Hoo <[email protected]>, Cornelius
Hoffmann <[email protected]>, Sezer BOZKIR <[email protected]>, Paul Martin
<[email protected]>, marko1olo <[email protected]>
+Maintainer-email: Sezer BOZKIR <[email protected]>, David Graham
<[email protected]>, Tom Clark <[email protected]>
License: BSD-2-Clause
Project-URL: Homepage, https://github.com/django-guardian/django-guardian
Project-URL: Documentation, https://django-guardian.readthedocs.io/
Project-URL: Repository, https://github.com/django-guardian/django-guardian
-Project-URL: Issues, https://github.com/me/spam/issues
+Project-URL: Issues, https://github.com/django-guardian/django-guardian/issues
Project-URL: Changelog,
https://github.com/django-guardian/django-guardian/releases
Keywords: django,permissions,authorization,object,row,level
Classifier: Development Status :: 5 - Production/Stable
@@ -56,7 +55,13 @@
Online documentation is available at
[https://django-guardian.readthedocs.io/](https://django-guardian.readthedocs.io/).
-**See real-world usage**: Check out [Who Uses
Guardian](https://django-guardian.readthedocs.io/en/latest/who-uses-guardian/)
to see how thousands of projects worldwide use django-guardian in production.
+**See real-world usage**: Check out [Who Uses
Guardian](https://django-guardian.readthedocs.io/en/stable/who-uses-guardian/)
to see how thousands of projects worldwide use django-guardian in production.
+
+## Contributing
+
+Please read
[`CONTRIBUTING.md`](https://github.com/django-guardian/django-guardian/blob/main/CONTRIBUTING.md)
before opening a pull request. It documents the repository's issue-first
workflow, branch targeting rules, local `uv` setup, required test and lint
commands, documentation expectations, and AI/LLM contribution policy.
+
+If you need to report a security issue, do **not** open a public issue. Follow
[`SECURITY.md`](https://github.com/django-guardian/django-guardian/blob/main/SECURITY.md)
and use the repository's [private vulnerability reporting
flow](https://github.com/django-guardian/django-guardian/security).
## Installation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_guardian-3.3.1/guardian/core.py
new/django_guardian-3.3.4/guardian/core.py
--- old/django_guardian-3.3.1/guardian/core.py 2026-03-30 11:44:38.000000000
+0200
+++ new/django_guardian-3.3.4/guardian/core.py 2026-08-27 02:04:37.298163400
+0200
@@ -122,7 +122,7 @@
return user_filters
- def get_user_perms(self, obj: Model) -> QuerySet[Permission]:
+ def get_user_perms(self, obj: Model) -> QuerySet[str]:
if self.user and not self.user.is_active:
return Permission.objects.none().values_list("codename", flat=True)
@@ -131,11 +131,11 @@
perms_qs = Permission.objects.filter(content_type=ctype)
user_filters = self.get_user_filters(obj)
user_perms_qs = perms_qs.filter(**user_filters)
- user_perms: QuerySet[Permission] =
user_perms_qs.values_list("codename", flat=True)
+ user_perms: QuerySet[str] = user_perms_qs.values_list("codename",
flat=True)
return user_perms
- def get_group_perms(self, obj: Model) -> QuerySet[Permission]:
+ def get_group_perms(self, obj: Model) -> QuerySet[str]:
if self.user and not self.user.is_active:
return Permission.objects.none().values_list("codename", flat=True)
@@ -144,7 +144,7 @@
perms_qs = Permission.objects.filter(content_type=ctype)
group_filters = self.get_group_filters(obj)
group_perms_qs = perms_qs.filter(**group_filters)
- group_perms: QuerySet[Permission] =
group_perms_qs.values_list("codename", flat=True)
+ group_perms: QuerySet[str] = group_perms_qs.values_list("codename",
flat=True)
return group_perms
@@ -245,8 +245,8 @@
perms =
group_model.objects.filter(**group_filters).select_related("permission")
# initialize entry in '_obj_perms_cache' for all prefetched objects
- for obj in objects:
- key = self.get_local_cache_key(obj)
+ # The ctype is assumed above to be homogenous, so leave `objects`
unevaluated.
+ for key in ((ctype.id, pk) for pk in pks):
if key not in self._obj_perms_cache:
self._obj_perms_cache[key] = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_guardian-3.3.1/guardian/shortcuts.py
new/django_guardian-3.3.4/guardian/shortcuts.py
--- old/django_guardian-3.3.1/guardian/shortcuts.py 2026-03-30
11:44:38.000000000 +0200
+++ new/django_guardian-3.3.4/guardian/shortcuts.py 2026-08-27
02:04:37.301040400 +0200
@@ -63,6 +63,27 @@
return t[0]
+def _ensure_permission(perm: Union[Permission, str]) -> Permission:
+ """Converts string permission to the corresponding django model, if
required."""
+ if isinstance(perm, str):
+ try:
+ app_label, codename = perm.split(".", 1)
+ except ValueError:
+ raise ValueError(
+ f"For global permissions, first argument must be in format:
'app_label.codename' (is {perm!r})"
+ )
+ perm = Permission.objects.get(content_type__app_label=app_label,
codename=codename)
+ return perm
+
+
+def _normalize_perm(perm: Union[Permission, str]) -> Union[Permission, str]:
+ """Normalizes permission codename, if it is a string."""
+ if isinstance(perm, str) and "." in perm:
+ _, perm = perm.split(".", 1)
+
+ return perm
+
+
def assign_perm(
perm: Union[str, Permission],
user_or_group: Any,
@@ -123,15 +144,7 @@
user, group = get_identity(user_or_group)
# If obj is None we try to operate on global permissions
if obj is None:
- if not isinstance(perm, Permission):
- try:
- app_label, codename = perm.split(".", 1)
- except ValueError:
- raise ValueError(
- "For global permissions, first argument must be in format:
'app_label.codename' (is %r)" % perm
- )
- perm = Permission.objects.get(content_type__app_label=app_label,
codename=codename)
-
+ perm = _ensure_permission(perm)
if user:
user.user_permissions.add(perm)
return perm
@@ -139,10 +152,7 @@
group.permissions.add(perm)
return perm
- if not isinstance(perm, Permission):
- if "." in perm:
- app_label, perm = perm.split(".", 1)
-
+ perm = _normalize_perm(perm)
if isinstance(obj, (QuerySet, list)):
if isinstance(user_or_group, (QuerySet, list)):
raise MultipleIdentityAndObjectError("Only bulk operations on
either users/groups OR objects supported")
@@ -206,14 +216,7 @@
user, group = get_identity(user_or_group)
if obj is None:
- if not isinstance(perm, Permission):
- try:
- app_label, codename = perm.split(".", 1)
- except ValueError:
- raise ValueError(
- "For global permissions, first argument must be in format:
'app_label.codename' (is %r)" % perm
- )
- perm = Permission.objects.get(content_type__app_label=app_label,
codename=codename)
+ perm = _ensure_permission(perm)
if user:
user.user_permissions.remove(perm)
return None
@@ -221,9 +224,7 @@
group.permissions.remove(perm)
return None
- if not isinstance(perm, Permission):
- perm = perm.split(".")[-1]
-
+ perm = _normalize_perm(perm)
if isinstance(obj, list) and not obj:
return None
@@ -516,6 +517,56 @@
T = TypeVar("T", bound=Model)
+def _compute_codenames_and_ctype(
+ perms: Union[str, list[str]],
+) -> tuple[Optional[ContentType], set[str]]:
+ """Extracts ContentType and codenames from given list of permissions."""
+ if isinstance(perms, str):
+ perms = [perms]
+ ctype = None
+ app_label = None
+ codenames = set()
+
+ # Compute codenames and set and ctype if possible
+ for perm in perms:
+ if "." in perm:
+ new_app_label, codename = perm.split(".", 1)
+ if app_label is not None and app_label != new_app_label:
+ raise MixedContentTypeError(f"Given perms must have same app
label ({app_label} != {new_app_label})")
+ else:
+ app_label = new_app_label
+ else:
+ codename = perm
+ codenames.add(codename)
+ if app_label is not None:
+ new_ctype = _get_ct_cached(app_label, codename)
+ if ctype is not None and ctype != new_ctype:
+ raise MixedContentTypeError(f"ContentType was once computed to
be {ctype} and another one {new_ctype}")
+ else:
+ ctype = new_ctype
+
+ return ctype, codenames
+
+
+def _compute_queryset(
+ ctype: Optional[ContentType],
+ klass: Union[Type[T], Manager[T], QuerySet[T], None],
+) -> tuple[ContentType, QuerySet[T]]:
+ """Computes QuerySet and ContentType if still missing"""
+ if ctype is None and klass is not None:
+ queryset = _get_queryset(klass)
+ ctype = get_content_type(queryset.model)
+ elif ctype is not None and klass is None:
+ queryset = _get_queryset(ctype.model_class())
+ elif klass is None:
+ raise WrongAppError("Cannot determine content type")
+ else:
+ queryset = _get_queryset(klass)
+ if ctype != get_content_type(queryset.model):
+ raise MixedContentTypeError("Content type for given perms and
klass differs")
+ return ctype, queryset
+
+
def get_objects_for_user(
user: Any,
perms: Union[str, list[str]],
@@ -622,46 +673,8 @@
``Cast("pk", CharField())`` fallback, so models with any PK type are
supported without extra configuration.
"""
- if isinstance(perms, str):
- perms = [perms]
- ctype = None
- app_label = None
- codenames = set()
-
- # Compute codenames and set and ctype if possible
- for perm in perms:
- if "." in perm:
- new_app_label, codename = perm.split(".", 1)
- if app_label is not None and app_label != new_app_label:
- raise MixedContentTypeError(
- "Given perms must have same app label ({} !=
{})".format(app_label, new_app_label)
- )
- else:
- app_label = new_app_label
- else:
- codename = perm
- codenames.add(codename)
- if app_label is not None:
- new_ctype = new_ctype = _get_ct_cached(app_label, codename)
- if ctype is not None and ctype != new_ctype:
- raise MixedContentTypeError(
- "ContentType was once computed to be {} and another one
{}".format(ctype, new_ctype)
- )
- else:
- ctype = new_ctype
-
- # Compute queryset and ctype if still missing
- if ctype is None and klass is not None:
- queryset = _get_queryset(klass)
- ctype = get_content_type(queryset.model)
- elif ctype is not None and klass is None:
- queryset = _get_queryset(ctype.model_class())
- elif klass is None:
- raise WrongAppError("Cannot determine content type")
- else:
- queryset = _get_queryset(klass)
- if ctype != get_content_type(queryset.model):
- raise MixedContentTypeError("Content type for given perms and
klass differs")
+ ctype, codenames = _compute_codenames_and_ctype(perms)
+ ctype, queryset = _compute_queryset(ctype, klass)
# At this point, we should have both ctype and queryset and they should
# match which means: ctype.model_class() == queryset.model
@@ -753,37 +766,47 @@
values = user_obj_perms_queryset
handle_pk_field = _handle_pk_field(queryset)
- if handle_pk_field is not None:
+ use_str_pk_for_user = handle_pk_field is None or (
+ user_model.objects.is_generic() and _casts_to_bigint(handle_pk_field)
+ )
+ if not use_str_pk_for_user:
values = values.annotate(obj_pk=handle_pk_field(expression=field_pk))
field_pk = "obj_pk"
values = values.values_list(field_pk, flat=True)
- if handle_pk_field is not None:
- q = Q(pk__in=values)
- else:
+ str_pk_annotated = False
+ if use_str_pk_for_user:
queryset = queryset.annotate(str_pk=Cast("pk", CharField()))
+ str_pk_annotated = True
q = Q(str_pk__in=values)
+ else:
+ q = Q(pk__in=values)
if use_groups:
field_pk = group_fields[0]
values = groups_obj_perms_queryset
- if handle_pk_field is not None:
+ use_str_pk_for_group = handle_pk_field is None or (
+ group_model.objects.is_generic() and
_casts_to_bigint(handle_pk_field)
+ )
+ if not use_str_pk_for_group:
values =
values.annotate(obj_pk=handle_pk_field(expression=field_pk))
field_pk = "obj_pk"
values = values.values_list(field_pk, flat=True)
- if handle_pk_field is not None:
- q |= Q(pk__in=values)
- else:
+ if use_str_pk_for_group:
+ if not str_pk_annotated:
+ queryset = queryset.annotate(str_pk=Cast("pk", CharField()))
q |= Q(str_pk__in=values)
+ else:
+ q |= Q(pk__in=values)
return queryset.filter(q)
def get_objects_for_group(
group: Group,
perms: Union[str, list[str]],
- klass: Union[Model, Manager, QuerySet, None] = None,
+ klass: Union[Type[T], Manager[T], QuerySet[T], None] = None,
any_perm: bool = False,
accept_global_perms: bool = True,
-) -> QuerySet:
+) -> QuerySet[T]:
"""Get objects that a group has *all* the supplied permissions for.
Parameters:
@@ -849,46 +872,8 @@
``Cast("pk", CharField())`` fallback, so models with any PK type are
supported without extra configuration.
"""
- if isinstance(perms, str):
- perms = [perms]
- ctype = None
- app_label = None
- codenames = set()
-
- # Compute the codenames and set ctype if possible
- for perm in perms:
- if "." in perm:
- new_app_label, codename = perm.split(".", 1)
- if app_label is not None and app_label != new_app_label:
- raise MixedContentTypeError(
- "Given perms must have same app label ({} !=
{})".format(app_label, new_app_label)
- )
- else:
- app_label = new_app_label
- else:
- codename = perm
- codenames.add(codename)
- if app_label is not None:
- new_ctype = _get_ct_cached(app_label, codename)
- if ctype is not None and ctype != new_ctype:
- raise MixedContentTypeError(
- "ContentType was once computed to be {} and another one
{}".format(ctype, new_ctype)
- )
- else:
- ctype = new_ctype
-
- # Compute queryset and ctype if still missing
- if ctype is None and klass is not None:
- queryset = _get_queryset(klass)
- ctype = get_content_type(queryset.model)
- elif ctype is not None and klass is None:
- queryset = _get_queryset(ctype.model_class())
- elif klass is None:
- raise WrongAppError("Cannot determine content type")
- else:
- queryset = _get_queryset(klass)
- if ctype != get_content_type(queryset.model):
- raise MixedContentTypeError("Content type for given perms and
klass differs")
+ ctype, codenames = _compute_codenames_and_ctype(perms)
+ ctype, queryset = _compute_queryset(ctype, klass)
# At this point, we should have both ctype and queryset and they should
# match which means: ctype.model_class() == queryset.model
@@ -935,14 +920,15 @@
values = groups_obj_perms_queryset
handle_pk_field = _handle_pk_field(queryset)
- if handle_pk_field is not None:
+ use_str_pk = handle_pk_field is None or (group_model.objects.is_generic()
and _casts_to_bigint(handle_pk_field))
+ if not use_str_pk:
values = values.annotate(obj_pk=handle_pk_field(expression=field_pk))
field_pk = "obj_pk"
else:
queryset = queryset.annotate(str_pk=Cast("pk", CharField()))
values = values.values_list(field_pk, flat=True)
- if handle_pk_field is not None:
+ if not use_str_pk:
return queryset.filter(pk__in=values)
else:
return queryset.filter(str_pk__in=values)
@@ -980,6 +966,14 @@
return None
+def _casts_to_bigint(handle_pk_field):
+ return (
+ isinstance(handle_pk_field, partial)
+ and handle_pk_field.func is Cast
+ and isinstance(handle_pk_field.keywords.get("output_field"),
BigIntegerField)
+ )
+
+
def filter_perms_queryset_by_objects(perms_queryset, objects):
if isinstance(objects, QuerySet):
field = "content_object__pk"
@@ -987,12 +981,18 @@
field = "object_pk"
handle_pk_field = _handle_pk_field(objects)
if handle_pk_field is not None:
- objects = objects.values(_pk=Cast(handle_pk_field("pk"),
output_field=CharField()))
- # Apply the same transformation to the object_pk field for
consistent comparison (#930)
- perms_queryset = perms_queryset.annotate(
- _transformed_object_pk=Cast(handle_pk_field(field),
output_field=CharField())
- )
- field = "_transformed_object_pk"
+ if _casts_to_bigint(handle_pk_field):
+ # Keep object_pk untouched to avoid CAST(object_pk AS
bigint)
+ # scans on large guardian tables. We only stringify model
PKs.
+ objects = objects.values(_pk=Cast("pk",
output_field=CharField()))
+ field = "object_pk"
+ else:
+ objects = objects.values(_pk=Cast(handle_pk_field("pk"),
output_field=CharField()))
+ # Apply the same transformation to the object_pk field for
consistent comparison (#930)
+ perms_queryset = perms_queryset.annotate(
+ _transformed_object_pk=Cast(handle_pk_field(field),
output_field=CharField())
+ )
+ field = "_transformed_object_pk"
else:
objects = objects.values("pk")
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_guardian-3.3.1/guardian/testapp/tests/test_core.py
new/django_guardian-3.3.4/guardian/testapp/tests/test_core.py
--- old/django_guardian-3.3.1/guardian/testapp/tests/test_core.py
2026-03-30 11:44:38.000000000 +0200
+++ new/django_guardian-3.3.4/guardian/testapp/tests/test_core.py
2026-08-27 02:04:37.303665900 +0200
@@ -6,7 +6,9 @@
from django.contrib.auth.management import create_permissions
from django.contrib.auth.models import AnonymousUser, Group, Permission
from django.contrib.contenttypes.models import ContentType
+from django.db import connection
from django.test import TestCase
+from django.test.utils import CaptureQueriesContext
from guardian.conf import settings as guardian_settings
from guardian.core import ObjectPermissionChecker
@@ -610,3 +612,13 @@
self.assertGreater(len(connection.queries), query_count)
finally:
settings.DEBUG = False
+
+ def test_prefetch_performance(self):
+ checker = ObjectPermissionChecker(self.user)
+ with CaptureQueriesContext(connection) as queries:
+ checker.prefetch_perms(Group.objects.all())
+
+ auth_group_from = "FROM %s" % connection.ops.quote_name("auth_group")
+ # Ignore other queries that JOIN this table.
+ group_selects = [q for q in queries if
q["sql"].endswith(auth_group_from)]
+ self.assertEqual(len(group_selects), 1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_guardian-3.3.1/guardian/testapp/tests/test_shortcuts.py
new/django_guardian-3.3.4/guardian/testapp/tests/test_shortcuts.py
--- old/django_guardian-3.3.1/guardian/testapp/tests/test_shortcuts.py
2026-03-30 11:44:38.000000000 +0200
+++ new/django_guardian-3.3.4/guardian/testapp/tests/test_shortcuts.py
2026-08-27 02:04:37.304000600 +0200
@@ -1,3 +1,4 @@
+import re
from unittest import mock
import warnings
@@ -17,6 +18,7 @@
WrongAppError,
)
from guardian.models import Group, Permission
+import guardian.shortcuts as guardian_shortcuts
from guardian.shortcuts import (
_get_ct_cached,
assign,
@@ -48,6 +50,16 @@
return ContentType.objects.get_for_model(Group)
+def assert_query_does_not_cast_object_pk(testcase, query):
+ testcase.assertIsNone(
+ re.search(
+ r"(cast\([^)]*\bobject_pk\b[^)]*\))|"
+ r"(\bobject_pk\b::[a-z_][a-z0-9_]*)",
+ query,
+ )
+ )
+
+
class ShortcutsTests(ObjectPermissionTestCase):
def test_get_perms_for_model(self):
expected_perms_amount = 3 if django.VERSION < (2, 1) else 4
@@ -402,6 +414,16 @@
perm_obj = Permission.objects.get(codename=codename,
content_type__app_label=app_label)
self.assertFalse(perm_obj in self.group.permissions.all())
+ def test_remove_perm_with_dots(self):
+ Permission.objects.create(
+ codename="contenttype.reorder",
+ content_type=ContentType.objects.get_for_model(self.ctype),
+ )
+
+ assign_perm("contenttypes.contenttype.reorder", self.user, self.ctype)
+ remove_perm("contenttypes.contenttype.reorder", self.user, self.ctype)
+
self.assertFalse(self.user.has_perm("contenttypes.contenttype.reorder",
self.ctype))
+
class MultipleIdentitiesRemoveTest(ObjectPermissionTestCase):
"""
@@ -723,6 +745,50 @@
{self.user1, admin},
)
+ def test_get_group_perms_consistent_inactive_user(self):
+ """Test that the get_group_perms shortcut returns the same results as
the
+ get_group_perms method of ObjectPermissionChecker for inactive user in
a group."""
+
+ self.user1.groups.add(self.group1)
+ assign_perm("change_contenttype", self.group1, self.obj1)
+
+ checker = ObjectPermissionChecker(self.user1)
+
+ get_group_perms_result_active = checker.get_group_perms(self.obj1)
+ shortcuts_get_group_perms_result_active = get_group_perms(self.user1,
self.obj1)
+
+ self.user1.is_active = False
+ self.user1.save()
+
+ get_group_perms_result_inactive = checker.get_group_perms(self.obj1)
+ shortcuts_get_group_perms_result_inactive =
get_group_perms(self.user1, self.obj1)
+
+ self.assertTrue(
+ set(get_group_perms_result_active) ==
set(shortcuts_get_group_perms_result_active) == {"change_contenttype"}
+ )
+ self.assertTrue(set(get_group_perms_result_inactive) ==
set(shortcuts_get_group_perms_result_inactive) == set())
+
+ def test_get_perms_consistent_for_inactive_user_in_group(self):
+ """Test that the get_perms shortcut returns the same results as the
+ get_perms method of ObjectPermissionChecker for inactive user in a
group."""
+
+ self.user1.groups.add(self.group1)
+ assign_perm("change_contenttype", self.group1, self.obj1)
+
+ checker = ObjectPermissionChecker(self.user1)
+
+ get_perms_result_active = checker.get_perms(self.obj1)
+ shortcuts_get_perms_result_active = get_perms(self.user1, self.obj1)
+
+ self.user1.is_active = False
+ self.user1.save()
+
+ get_perms_result_inactive = checker.get_perms(self.obj1)
+ shortcuts_get_perms_result_inactive = get_perms(self.user1, self.obj1)
+
+ self.assertTrue(get_perms_result_active ==
shortcuts_get_perms_result_active == ["change_contenttype"])
+ self.assertTrue(get_perms_result_inactive ==
shortcuts_get_perms_result_inactive == [])
+
def test_without_group_users(self):
self.user1.groups.add(self.group1)
self.user2.groups.add(self.group2)
@@ -1021,6 +1087,28 @@
objects = get_objects_for_user(self.user, ["auth.change_group"],
Group.objects.all())
self.assertEqual([obj.name for obj in objects], [self.group.name])
+ def test_generic_subquery_does_not_cast_object_pk(self):
+ assign_perm("auth.change_group", self.user, self.group)
+ other_group = Group.objects.create(name="group2")
+
+ objects = get_objects_for_user(self.user, ["auth.change_group"])
+ self.assertEqual(set(objects), {self.group})
+ self.assertNotIn(other_group, objects)
+
+ query = str(objects.query).lower()
+ assert_query_does_not_cast_object_pk(self, query)
+
+ def test_generic_subquery_does_not_cast_object_pk_for_queryset_klass(self):
+ assign_perm("auth.change_group", self.user, self.group)
+ other_group = Group.objects.create(name="group2")
+
+ objects = get_objects_for_user(self.user, ["auth.change_group"],
Group.objects.all())
+ self.assertEqual(set(objects), {self.group})
+ self.assertNotIn(other_group, objects)
+
+ query = str(objects.query).lower()
+ assert_query_does_not_cast_object_pk(self, query)
+
def test_ensure_returns_queryset(self):
objects = get_objects_for_user(self.user, ["auth.change_group"])
self.assertTrue(isinstance(objects, QuerySet))
@@ -1505,6 +1593,32 @@
objects = get_objects_for_group(self.group1, ["change_contenttype"],
ContentType.objects.all())
self.assertEqual(list(objects), [self.obj1])
+ def test_generic_subquery_does_not_cast_object_pk(self):
+ assign_perm("contenttypes.change_contenttype", self.group1, self.obj1)
+ obj4 = ContentType.objects.create(model="qux",
app_label="guardian-tests")
+
+ objects = get_objects_for_group(self.group1,
["contenttypes.change_contenttype"])
+ self.assertEqual(set(objects), {self.obj1})
+ self.assertNotIn(obj4, objects)
+
+ query = str(objects.query).lower()
+ assert_query_does_not_cast_object_pk(self, query)
+
+ def test_generic_subquery_does_not_cast_object_pk_for_queryset_klass(self):
+ assign_perm("contenttypes.change_contenttype", self.group1, self.obj1)
+ obj4 = ContentType.objects.create(model="qux",
app_label="guardian-tests")
+
+ with mock.patch(
+ "guardian.shortcuts._casts_to_bigint",
+ wraps=guardian_shortcuts._casts_to_bigint,
+ ) as mocked_casts_to_bigint:
+ objects = get_objects_for_group(self.group1,
["change_contenttype"], ContentType.objects.all())
+ mocked_casts_to_bigint.assert_called()
+ query = str(objects.query).lower()
+ self.assertEqual(set(objects), {self.obj1})
+ self.assertNotIn(obj4, objects)
+ assert_query_does_not_cast_object_pk(self, query)
+
def test_ensure_returns_queryset(self):
objects = get_objects_for_group(self.group1,
["contenttypes.change_contenttype"])
self.assertTrue(isinstance(objects, QuerySet))
@@ -1813,6 +1927,56 @@
f"get_perms {all_perms} should be superset of get_group_perms
{group_perms}",
)
+ def test_get_user_perms_consistent_inactive_user(self):
+ """Test that get_user_perms in ObjectPermissionChecker and the
get_user_perms shortcut
+ return consistent results for an inactive user."""
+
+ assign_perm("change_contenttype", self.user, self.obj)
+
+ checker = ObjectPermissionChecker(self.user)
+
+ get_user_perms_result_active = checker.get_user_perms(self.obj)
+ shortcuts_get_user_perms_result_active = get_user_perms(self.user,
self.obj)
+
+ # Make user inactive
+ self.user.is_active = False
+ self.user.save()
+
+ get_user_perms_result_inactive = checker.get_user_perms(self.obj)
+ shortcuts_get_user_perms_result_inactive = get_user_perms(self.user,
self.obj)
+
+ self.assertTrue(
+ set(get_user_perms_result_active) ==
set(shortcuts_get_user_perms_result_active) == {"change_contenttype"},
+ )
+ self.assertTrue(
+ set(get_user_perms_result_inactive) ==
set(shortcuts_get_user_perms_result_inactive) == set(),
+ )
+
+ def test_get_perms_consistent_inactive_user(self):
+ """Test that get_perms in ObjectPermissionChecker and the get_perms
shortcut
+ return consistent results for an inactive user."""
+
+ assign_perm("change_contenttype", self.user, self.obj)
+
+ checker = ObjectPermissionChecker(self.user)
+
+ get_perms_result_active = checker.get_perms(self.obj)
+ shortcuts_get_perms_result_active = get_perms(self.user, self.obj)
+
+ # Make user inactive
+ self.user.is_active = False
+ self.user.save()
+
+ get_perms_result_inactive = checker.get_perms(self.obj)
+ shortcuts_get_perms_result_inactive = get_perms(self.user, self.obj)
+
+ self.assertTrue(
+ get_perms_result_active == shortcuts_get_perms_result_active ==
["change_contenttype"],
+ )
+ self.assertTrue(
+ get_perms_result_inactive == shortcuts_get_perms_result_inactive
== [],
+ )
+
def test_superuser_behavior(self):
"""Test behavior with superuser."""
superuser = User.objects.create_superuser(username="superuser",
email="[email protected]", password="pass")
@@ -2219,6 +2383,7 @@
uuid_objects = get_objects_for_user(self.user,
"testapp.add_uuidpkmodel")
self.assertEqual(len(uuid_objects), 1)
+ self.assertEqual(set(uuid_objects.values_list("pk", flat=True)),
{uuid_obj1.pk})
# Test with TextField (non-standard, uses fix)
text_obj1 = TextPKModel.objects.create(text_pk="text-id-1")
@@ -2228,6 +2393,7 @@
text_objects = get_objects_for_user(self.user,
"testapp.add_textpkmodel")
self.assertEqual(len(text_objects), 1)
+ self.assertEqual(set(text_objects.values_list("pk", flat=True)),
{text_obj1.pk})
# Both should behave consistently
self.assertEqual(len(uuid_objects), len(text_objects))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_guardian-3.3.1/pyproject.toml
new/django_guardian-3.3.4/pyproject.toml
--- old/django_guardian-3.3.1/pyproject.toml 2026-03-30 11:44:38.000000000
+0200
+++ new/django_guardian-3.3.4/pyproject.toml 2026-08-27 02:04:37.305000500
+0200
@@ -1,6 +1,6 @@
[project]
name = "django-guardian"
-version = "3.3.1"
+version = "3.3.4"
description = "Per object permissions for Django"
keywords = ["django", "permissions", "authorization", "object", "row", "level"]
readme = "README.md"
@@ -95,24 +95,24 @@
{ name = "Richard de Wit", email = "[email protected]" },
{ name = "Pedro Rojas Gavidia", email = "[email protected]" },
{ name = "Rainshaw Gao", email = "[email protected]" },
- {name = "Tom Clark", email = "[email protected]"},
+ { name = "Tom Clark", email = "[email protected]"},
+ { name = "David Graham", email = "[email protected]" },
{ name = "Meredith Hoo", email = "[email protected]" },
{ name = "Chris Maggiuli" },
{ name = "Bona Fide IT GmbH"},
{ name = "Columbia University, SIS Development and Application Services" },
{ name = "Cornelius Hoffmann", email = "[email protected]" },
{ name = "Sezer BOZKIR", email = "[email protected]"},
+ { name = "Paul Martin", email = "[email protected]"},
+ { name = "marko1olo", email = "[email protected]" },
+ { name = "Michael K." },
+ { name = "Chris Maggiuli" },
+ { name = "Bona Fide IT GmbH" },
]
maintainers = [
- {name = "Tom Clark", email = "[email protected]"},
- { name = "Brian May", email = "[email protected]" },
- { name = "Lukasz Balcerzak", email = "[email protected]" },
- { name = "Adam Dobrawy", email = "[email protected]" },
+ { name = "Sezer BOZKIR", email = "[email protected]" },
{ name = "David Graham", email = "[email protected]" },
- { name = "Michael K." },
- { name = "Chris Maggiuli" },
- { name = "Bona Fide IT GmbH"},
- { name = "Sezer BOZKIR", email = "[email protected]"},
+ { name = "Tom Clark", email = "[email protected]" },
]
dependencies = [
@@ -124,7 +124,7 @@
Homepage = "https://github.com/django-guardian/django-guardian"
Documentation = "https://django-guardian.readthedocs.io/"
Repository = "https://github.com/django-guardian/django-guardian"
-Issues = "https://github.com/me/spam/issues"
+Issues = "https://github.com/django-guardian/django-guardian/issues"
Changelog = "https://github.com/django-guardian/django-guardian/releases"
[dependency-groups]