Skip to content

Commit a891d8a

Browse files
committed
django 5.x support
1 parent 39bff05 commit a891d8a

File tree

13 files changed

+839
-27
lines changed

13 files changed

+839
-27
lines changed

.github/workflows/tests.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ jobs:
2626
strategy:
2727
fail-fast: false
2828
matrix:
29-
python-version: [ "3.9", "3.11"]
30-
django-version: [ "3.2", "4.2"]
29+
python-version: [ "3.10", "3.11", "3.12"]
30+
django-version: [ "3.2", "4.2", "5.0"]
3131
db-engine: ["pg", "mysql"]
3232
env:
3333
PY_VER: ${{ matrix.python-version}}

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name = "pypi"
77

88
[dev-packages]
99
check-manifest = "*"
10-
django = "*"
10+
django = "*"
1111
django-reversion = "*"
1212
django-webtest = "*"
1313
mock = "*"

setup.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,28 @@ def run_tests(self):
4848
include_package_data=True,
4949
description='Optimistic lock implementation for Django. Prevents users from doing concurrent editing.',
5050
long_description=open('README.md').read(),
51+
extras_require={
52+
'dev': [
53+
'black',
54+
'check-manifest',
55+
'django',
56+
'django-reversion',
57+
'django-webtest',
58+
'flake8',
59+
'mock',
60+
'pre-commit',
61+
'psycopg2-binary',
62+
'pytest',
63+
'pytest-cov',
64+
'pytest-django',
65+
'pytest-echo',
66+
'tox',
67+
'twine',
68+
'sphinx',
69+
'sphinx-issues',
70+
'sphinx-autobuild',
71+
]
72+
},
5173
long_description_content_type='text/markdown',
5274
license='MIT License',
5375
keywords='django, concurrency, optimistic lock, locking, concurrent editing',

src/concurrency/admin.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
from concurrency.exceptions import RecordModifiedError
2424
from concurrency.forms import ConcurrentForm, VersionWidget
2525
from concurrency.utils import flatten
26-
26+
from django.forms import CheckboxInput
27+
from django.utils.html import format_html
28+
from django.utils.translation import gettext_lazy as _
2729
ALL = object()
2830

2931

@@ -35,8 +37,16 @@ def action_checkbox(self, obj):
3537
A list_display column containing a checkbox widget.
3638
"""
3739
if self.check_concurrent_action:
38-
return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME,
39-
force_str("%s,%s" % (obj.pk, get_revision_of_object(obj))))
40+
attrs = {
41+
"class": "action-select",
42+
"aria-label": format_html(_("Select this object for an action - {}"), obj),
43+
}
44+
checkbox = CheckboxInput(attrs, lambda value: False)
45+
pk = force_str("%s,%s" % (obj.pk, get_revision_of_object(obj)))
46+
return checkbox.render(helpers.ACTION_CHECKBOX_NAME, pk)
47+
48+
# return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME,
49+
# force_str("%s,%s" % (obj.pk, get_revision_of_object(obj))))
4050
else: # pragma: no cover
4151
return super().action_checkbox(obj)
4252

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Generated by Django 5.0.1 on 2024-01-25 17:49
2+
3+
import django.contrib.auth.models
4+
import django.contrib.auth.validators
5+
import django.db.models.deletion
6+
import django.utils.timezone
7+
from django.db import migrations, models
8+
9+
10+
class Migration(migrations.Migration):
11+
12+
initial = True
13+
14+
dependencies = [
15+
("contenttypes", "0002_remove_content_type_name"),
16+
]
17+
18+
operations = [
19+
migrations.CreateModel(
20+
name="Permission",
21+
fields=[
22+
(
23+
"id",
24+
models.AutoField(
25+
auto_created=True,
26+
primary_key=True,
27+
serialize=False,
28+
verbose_name="ID",
29+
),
30+
),
31+
("name", models.CharField(max_length=255, verbose_name="name")),
32+
("codename", models.CharField(max_length=100, verbose_name="codename")),
33+
(
34+
"content_type",
35+
models.ForeignKey(
36+
on_delete=django.db.models.deletion.CASCADE,
37+
to="contenttypes.contenttype",
38+
verbose_name="content type",
39+
),
40+
),
41+
],
42+
options={
43+
"verbose_name": "permission",
44+
"verbose_name_plural": "permissions",
45+
"ordering": [
46+
"content_type__app_label",
47+
"content_type__model",
48+
"codename",
49+
],
50+
"unique_together": {("content_type", "codename")},
51+
},
52+
managers=[
53+
("objects", django.contrib.auth.models.PermissionManager()),
54+
],
55+
),
56+
migrations.CreateModel(
57+
name="Group",
58+
fields=[
59+
(
60+
"id",
61+
models.AutoField(
62+
auto_created=True,
63+
primary_key=True,
64+
serialize=False,
65+
verbose_name="ID",
66+
),
67+
),
68+
(
69+
"name",
70+
models.CharField(max_length=150, unique=True, verbose_name="name"),
71+
),
72+
(
73+
"permissions",
74+
models.ManyToManyField(
75+
blank=True, to="auth.permission", verbose_name="permissions"
76+
),
77+
),
78+
],
79+
options={
80+
"verbose_name": "group",
81+
"verbose_name_plural": "groups",
82+
},
83+
managers=[
84+
("objects", django.contrib.auth.models.GroupManager()),
85+
],
86+
),
87+
migrations.CreateModel(
88+
name="User",
89+
fields=[
90+
(
91+
"id",
92+
models.AutoField(
93+
auto_created=True,
94+
primary_key=True,
95+
serialize=False,
96+
verbose_name="ID",
97+
),
98+
),
99+
("password", models.CharField(max_length=128, verbose_name="password")),
100+
(
101+
"last_login",
102+
models.DateTimeField(
103+
blank=True, null=True, verbose_name="last login"
104+
),
105+
),
106+
(
107+
"is_superuser",
108+
models.BooleanField(
109+
default=False,
110+
help_text="Designates that this user has all permissions without explicitly assigning them.",
111+
verbose_name="superuser status",
112+
),
113+
),
114+
(
115+
"username",
116+
models.CharField(
117+
error_messages={
118+
"unique": "A user with that username already exists."
119+
},
120+
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
121+
max_length=150,
122+
unique=True,
123+
validators=[
124+
django.contrib.auth.validators.UnicodeUsernameValidator()
125+
],
126+
verbose_name="username",
127+
),
128+
),
129+
(
130+
"first_name",
131+
models.CharField(
132+
blank=True, max_length=150, verbose_name="first name"
133+
),
134+
),
135+
(
136+
"last_name",
137+
models.CharField(
138+
blank=True, max_length=150, verbose_name="last name"
139+
),
140+
),
141+
(
142+
"email",
143+
models.EmailField(
144+
blank=True, max_length=254, verbose_name="email address"
145+
),
146+
),
147+
(
148+
"is_staff",
149+
models.BooleanField(
150+
default=False,
151+
help_text="Designates whether the user can log into this admin site.",
152+
verbose_name="staff status",
153+
),
154+
),
155+
(
156+
"is_active",
157+
models.BooleanField(
158+
default=True,
159+
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
160+
verbose_name="active",
161+
),
162+
),
163+
(
164+
"date_joined",
165+
models.DateTimeField(
166+
default=django.utils.timezone.now, verbose_name="date joined"
167+
),
168+
),
169+
(
170+
"groups",
171+
models.ManyToManyField(
172+
blank=True,
173+
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
174+
related_name="user_set",
175+
related_query_name="user",
176+
to="auth.group",
177+
verbose_name="groups",
178+
),
179+
),
180+
(
181+
"user_permissions",
182+
models.ManyToManyField(
183+
blank=True,
184+
help_text="Specific permissions for this user.",
185+
related_name="user_set",
186+
related_query_name="user",
187+
to="auth.permission",
188+
verbose_name="user permissions",
189+
),
190+
),
191+
],
192+
options={
193+
"verbose_name": "user",
194+
"verbose_name_plural": "users",
195+
"abstract": False,
196+
"swappable": "AUTH_USER_MODEL",
197+
},
198+
managers=[
199+
("objects", django.contrib.auth.models.UserManager()),
200+
],
201+
),
202+
]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from django.db import migrations
2+
3+
from concurrency.fields import IntegerVersionField
4+
5+
6+
class Migration(migrations.Migration):
7+
"""
8+
To enabe this migration you must add this code to your settings
9+
10+
MIGRATION_MODULES = {
11+
...
12+
...
13+
'auth': 'tests.auth_migrations',
14+
}
15+
16+
"""
17+
dependencies = [
18+
('auth', '0001_initial'),
19+
]
20+
21+
operations = [
22+
migrations.AddField(
23+
model_name='Group',
24+
name='version',
25+
field=IntegerVersionField(help_text=b'Version', default=1),
26+
27+
),
28+
]

tests/demoapp/demo/auth_migrations5/__init__.py

Whitespace-only changes.

tests/demoapp/demo/backends.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import Any
2+
3+
from django.conf import settings
4+
from django.contrib.auth import get_user_model
5+
from django.contrib.auth.backends import ModelBackend
6+
7+
8+
class AnyUserAuthBackend(ModelBackend):
9+
def authenticate(self, request, username=None, password=None, **kwargs):
10+
if settings.DEBUG:
11+
if username.startswith("user"):
12+
user, __ = get_user_model().objects.update_or_create(
13+
username=username,
14+
defaults=dict(
15+
is_staff=False,
16+
is_active=True,
17+
is_superuser=False,
18+
email=f"{username}@demo.org",
19+
),
20+
)
21+
return user
22+
elif username.startswith("admin"):
23+
user, __ = get_user_model().objects.update_or_create(
24+
username=username,
25+
defaults=dict(
26+
is_staff=True,
27+
is_active=True,
28+
is_superuser=True,
29+
email=f"{username}@demo.org",
30+
),
31+
)
32+
return user
33+
return None

0 commit comments

Comments
 (0)