Skip to content

Commit 2f215b8

Browse files
authored
Merge pull request Arisophy#5 from Arisophy/develop
support for bulk_update
2 parents 58cc0a1 + ff92041 commit 2f215b8

File tree

7 files changed

+135
-17
lines changed

7 files changed

+135
-17
lines changed

compositepk-model/compositepk_model/urls.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
path('', views.home, name='home'),
1212
path('admin/', admin.site.urls),
1313
path('musician/', views.MusicianListView.as_view(), name='musician'),
14+
path('musician/set_profile_all', views.set_profile_all, name='set_profile_all'),
1415
path('artisit/<int:id>/album/', views.AlbumListView.as_view(), name='album'),
1516
path('artisit/<int:id>/album/add/', views.AlbumFormView.as_view(), name='add_album'),
17+
path('artisit/<int:id>/album/set_5stars/', views.set_5stars, name='set_5stars'),
1618
path('company/', views.CompanyListView.as_view(), name='company'),
1719
path('company/<int:id>/branch/',views.CompanyBranchListView.as_view(), name='companybranch'),
18-
path('company/<int:id>/branch/add', views.CompanyBranchFormView.as_view(),name='add_companybranch'),
19-
path('test_filter', views.test_filter,name='test_filter'),
20-
path('check_keys', views.check_keys,name='check_keys'),
20+
path('company/<int:id>/branch/add', views.CompanyBranchFormView.as_view(), name='add_companybranch'),
21+
path('test_filter', views.test_filter, name='test_filter'),
22+
path('check_keys', views.check_keys, name='check_keys'),
2123
]

compositepk-model/cpkmodel/cpkmodel.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def _set_cpk_val(self, value):
4747
def _no_check():
4848
return []
4949

50+
def get_pk_lookups(self):
51+
if self.has_compositepk:
52+
keys = self.pkeys
53+
vals = self.pkvals
54+
return { key.attname:val for key, val in zip(keys, vals)}
55+
else:
56+
return { 'pk':self.pk }
57+
5058

5159
###########################
5260
# override
@@ -101,6 +109,7 @@ def __new__(cls, name, bases, attrs, **kwargs):
101109
setattr(super_new, "delete", CPkModelMixin.delete)
102110
else:
103111
super_new.has_compositepk = False
112+
setattr(super_new, "get_pk_lookups", CPkModelMixin.get_pk_lookups)
104113
meta.base_manager._queryset_class = CPkQuerySet
105114
meta.default_manager._queryset_class = CPkQuerySet
106115
super_new.pkeys = pkeys

compositepk-model/cpkmodel/cpkquery.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import copy
22

3+
from django.db import connections,transaction
34
from django.db.models import QuerySet,Q
45
from django.db.models.sql import Query, DeleteQuery, UpdateQuery
56
from django.db.models.constants import LOOKUP_SEP
7+
from django.db.models.expressions import Case, Expression, Value, When
8+
from django.db.models.functions import Cast
69
from django.db.utils import NotSupportedError,ProgrammingError
710

811
from .constants import CPK_SEP
@@ -17,6 +20,17 @@ def _get_pk_names(self):
1720
# override
1821
###########################
1922

23+
def chain(self, klass=None):
24+
cpk_klass = klass
25+
if klass == Query:
26+
cpk_klass = CPkQuery
27+
elif klass == UpdateQuery:
28+
cpk_klass = CPkUpdateQuery
29+
elif klass == DeleteQuery:
30+
cpk_klass = CPkDeleteQuery
31+
32+
return super().chain(klass=cpk_klass)
33+
2034
def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False):
2135
meta = self.get_meta()
2236
first_name = names[0]
@@ -146,7 +160,64 @@ class CPkUpdateQuery(CPkQueryMixin, UpdateQuery):
146160

147161

148162
class CPkQuerySet(QuerySet):
163+
###########################
164+
# override
165+
###########################
166+
149167
def __init__(self, model=None, query=None, using=None, hints=None):
150168
if not query:
151169
query = CPkQuery(model)
152170
super().__init__(model, query, using, hints)
171+
172+
def bulk_update(self, objs, fields, batch_size=None):
173+
"""
174+
Update the given fields in each of the given objects in the database.
175+
"""
176+
if batch_size is not None and batch_size < 0:
177+
raise ValueError('Batch size must be a positive integer.')
178+
if not fields:
179+
raise ValueError('Field names must be given to bulk_update().')
180+
objs = tuple(objs)
181+
if any(obj.pk is None for obj in objs):
182+
raise ValueError('All bulk_update() objects must have a primary key set.')
183+
fields = [self.model._meta.get_field(name) for name in fields]
184+
if any(not f.concrete or f.many_to_many for f in fields):
185+
raise ValueError('bulk_update() can only be used with concrete fields.')
186+
if any(f.primary_key for f in fields):
187+
raise ValueError('bulk_update() cannot be used with primary key fields.')
188+
if not objs:
189+
return
190+
# PK is used twice in the resulting update query, once in the filter
191+
# and once in the WHEN. Each field will also have one CAST.
192+
max_batch_size = connections[self.db].ops.bulk_batch_size(['pk', 'pk'] + fields, objs)
193+
batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
194+
requires_casting = connections[self.db].features.requires_casted_case_in_updates
195+
batches = (objs[i:i + batch_size] for i in range(0, len(objs), batch_size))
196+
updates = []
197+
for batch_objs in batches:
198+
update_kwargs = {}
199+
for field in fields:
200+
when_statements = []
201+
for obj in batch_objs:
202+
attr = getattr(obj, field.attname)
203+
if not isinstance(attr, Expression):
204+
attr = Value(attr, output_field=field)
205+
# CHANGE S
206+
#when_statements.append(When(pk=obj.pk, then=attr))
207+
lookups = obj.get_pk_lookups()
208+
when_statements.append(When(**lookups, then=attr))
209+
# CHANGE E
210+
case_statement = Case(*when_statements, output_field=field)
211+
if requires_casting:
212+
case_statement = Cast(case_statement, output_field=field)
213+
update_kwargs[field.attname] = case_statement
214+
updates.append(([obj.pk for obj in batch_objs], update_kwargs))
215+
with transaction.atomic(using=self.db, savepoint=False):
216+
for pks, update_kwargs in updates:
217+
self.filter(pk__in=pks).update(**update_kwargs)
218+
bulk_update.alters_data = True
219+
220+
def _get_pk_condition(self, obj):
221+
conditions = {}
222+
223+
pass

compositepk-model/test/templates/app/album.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ <h2>Album List</h2>
9595
{% endfor %}
9696
</tbody>
9797
</table>
98+
{% if object_list|length > 0 %}
99+
<div><a class="btn btn-primary" href="{% url 'set_5stars' object_list.0.artist_id %}" role="button">set 5stars to all(bulk_update)</a></div>
100+
{% endif %}
98101
</div>
99102
</div>
100103

101-
102104
{% endblock %}
103-

compositepk-model/test/templates/app/check_keys.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ <h2>{{ obj.name }}</h2>
2121
</tr>
2222
</thead>
2323
<tbody>
24+
<tr>
25+
<td>pk</td>
26+
<td>{{ obj.pkval }}</td>
27+
</tr>
2428
<tr>
2529
<td>_meta.pk</td>
2630
<td>{{ obj.meta.pk }}</td>

compositepk-model/test/templates/app/musician.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ <h2>Musician List</h2>
7575
{% endfor %}
7676
</tbody>
7777
</table>
78+
{% if object_list|length > 0 %}
79+
<div>
80+
<form action="{% url 'set_profile_all' %}" method="post">
81+
{% csrf_token %}
82+
<input type="text" name="profile" value="">
83+
<input type="submit" class="btn btn-primary" role="button" value="set profile to all(bulk_update)">
84+
</form>
85+
</div>
86+
{% endif %}
7887
</div>
7988
</div>
8089
</div>

compositepk-model/test/views.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
"""
44

55
from datetime import datetime
6+
67
from django.shortcuts import render
7-
from django.http import HttpRequest
8+
from django.http import HttpRequest,HttpResponseRedirect
89
from django.views.generic.list import ListView
910
from django.views.generic.edit import FormView
1011
from django.db.models import Max
1112
from django.db import DatabaseError, InterfaceError
13+
1214
from test.models import (
1315
Musician,
1416
Album,
@@ -20,7 +22,6 @@
2022
CompanyBranchForm,
2123
)
2224

23-
2425
################
2526
# home
2627
################
@@ -39,6 +40,16 @@ class MusicianListView(ListView):
3940
template_name = 'app/musician.html'
4041
model = Musician
4142

43+
def set_profile_all(request):
44+
""" bulk_update test """
45+
musicians = Musician.objects.all()
46+
prof = request.POST.get('profile')
47+
for musician in musicians:
48+
musician.profile = prof
49+
Musician.objects.bulk_update(musicians, ['profile',])
50+
51+
return HttpResponseRedirect('/musician/')
52+
4253

4354
################
4455
# Album
@@ -56,7 +67,6 @@ def get_queryset(self):
5667
# filter pattern(2) : artist=obj
5768
return super().get_queryset().filter(artist=musician)
5869

59-
6070
class AlbumFormView(FormView):
6171
template_name = 'app/album_create.html'
6272
form_class = AlbumForm
@@ -105,6 +115,14 @@ def form_valid(self, form):
105115
album.save()
106116
return super().form_valid(form)
107117

118+
def set_5stars(request, id):
119+
""" bulk_update test """
120+
albums = Album.objects.filter(artist_id=id)
121+
for album in albums:
122+
album.num_stars = 5
123+
Album.objects.bulk_update(albums, ['num_stars',])
124+
125+
return HttpResponseRedirect("/artisit/{}/album/".format(id))
108126

109127
################
110128
# Company
@@ -247,15 +265,19 @@ def exec_test(no, key, val, memo):
247265
################
248266
def check_keys(request):
249267
def make_result(name, obj):
250-
col = obj._meta.pk.get_col(name)
251-
result = {
252-
'name':name,
253-
'meta':obj._meta,
254-
'pk':obj._meta.pk,
255-
'pk_cls':obj._meta.pk.__class__.__name__,
256-
'col':col,
257-
}
258-
return result
268+
if obj:
269+
col = obj._meta.pk.get_col(name)
270+
result = {
271+
'name':name,
272+
'meta':obj._meta,
273+
'pk':obj._meta.pk,
274+
'pk_cls':obj._meta.pk.__class__.__name__,
275+
'col':col,
276+
'pkval':obj.pk,
277+
}
278+
return result
279+
else:
280+
return None
259281

260282
"""Renders the home page."""
261283
assert isinstance(request, HttpRequest)

0 commit comments

Comments
 (0)