Skip to content

Commit

Permalink
Merge pull request #5 from Arisophy/develop
Browse files Browse the repository at this point in the history
support for bulk_update
  • Loading branch information
Arisophy authored Mar 13, 2021
2 parents 58cc0a1 + ff92041 commit 2f215b8
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 17 deletions.
8 changes: 5 additions & 3 deletions compositepk-model/compositepk_model/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
path('', views.home, name='home'),
path('admin/', admin.site.urls),
path('musician/', views.MusicianListView.as_view(), name='musician'),
path('musician/set_profile_all', views.set_profile_all, name='set_profile_all'),
path('artisit/<int:id>/album/', views.AlbumListView.as_view(), name='album'),
path('artisit/<int:id>/album/add/', views.AlbumFormView.as_view(), name='add_album'),
path('artisit/<int:id>/album/set_5stars/', views.set_5stars, name='set_5stars'),
path('company/', views.CompanyListView.as_view(), name='company'),
path('company/<int:id>/branch/',views.CompanyBranchListView.as_view(), name='companybranch'),
path('company/<int:id>/branch/add', views.CompanyBranchFormView.as_view(),name='add_companybranch'),
path('test_filter', views.test_filter,name='test_filter'),
path('check_keys', views.check_keys,name='check_keys'),
path('company/<int:id>/branch/add', views.CompanyBranchFormView.as_view(), name='add_companybranch'),
path('test_filter', views.test_filter, name='test_filter'),
path('check_keys', views.check_keys, name='check_keys'),
]
9 changes: 9 additions & 0 deletions compositepk-model/cpkmodel/cpkmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def _set_cpk_val(self, value):
def _no_check():
return []

def get_pk_lookups(self):
if self.has_compositepk:
keys = self.pkeys
vals = self.pkvals
return { key.attname:val for key, val in zip(keys, vals)}
else:
return { 'pk':self.pk }


###########################
# override
Expand Down Expand Up @@ -101,6 +109,7 @@ def __new__(cls, name, bases, attrs, **kwargs):
setattr(super_new, "delete", CPkModelMixin.delete)
else:
super_new.has_compositepk = False
setattr(super_new, "get_pk_lookups", CPkModelMixin.get_pk_lookups)
meta.base_manager._queryset_class = CPkQuerySet
meta.default_manager._queryset_class = CPkQuerySet
super_new.pkeys = pkeys
Expand Down
71 changes: 71 additions & 0 deletions compositepk-model/cpkmodel/cpkquery.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import copy

from django.db import connections,transaction
from django.db.models import QuerySet,Q
from django.db.models.sql import Query, DeleteQuery, UpdateQuery
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import Case, Expression, Value, When
from django.db.models.functions import Cast
from django.db.utils import NotSupportedError,ProgrammingError

from .constants import CPK_SEP
Expand All @@ -17,6 +20,17 @@ def _get_pk_names(self):
# override
###########################

def chain(self, klass=None):
cpk_klass = klass
if klass == Query:
cpk_klass = CPkQuery
elif klass == UpdateQuery:
cpk_klass = CPkUpdateQuery
elif klass == DeleteQuery:
cpk_klass = CPkDeleteQuery

return super().chain(klass=cpk_klass)

def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False):
meta = self.get_meta()
first_name = names[0]
Expand Down Expand Up @@ -146,7 +160,64 @@ class CPkUpdateQuery(CPkQueryMixin, UpdateQuery):


class CPkQuerySet(QuerySet):
###########################
# override
###########################

def __init__(self, model=None, query=None, using=None, hints=None):
if not query:
query = CPkQuery(model)
super().__init__(model, query, using, hints)

def bulk_update(self, objs, fields, batch_size=None):
"""
Update the given fields in each of the given objects in the database.
"""
if batch_size is not None and batch_size < 0:
raise ValueError('Batch size must be a positive integer.')
if not fields:
raise ValueError('Field names must be given to bulk_update().')
objs = tuple(objs)
if any(obj.pk is None for obj in objs):
raise ValueError('All bulk_update() objects must have a primary key set.')
fields = [self.model._meta.get_field(name) for name in fields]
if any(not f.concrete or f.many_to_many for f in fields):
raise ValueError('bulk_update() can only be used with concrete fields.')
if any(f.primary_key for f in fields):
raise ValueError('bulk_update() cannot be used with primary key fields.')
if not objs:
return
# PK is used twice in the resulting update query, once in the filter
# and once in the WHEN. Each field will also have one CAST.
max_batch_size = connections[self.db].ops.bulk_batch_size(['pk', 'pk'] + fields, objs)
batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
requires_casting = connections[self.db].features.requires_casted_case_in_updates
batches = (objs[i:i + batch_size] for i in range(0, len(objs), batch_size))
updates = []
for batch_objs in batches:
update_kwargs = {}
for field in fields:
when_statements = []
for obj in batch_objs:
attr = getattr(obj, field.attname)
if not isinstance(attr, Expression):
attr = Value(attr, output_field=field)
# CHANGE S
#when_statements.append(When(pk=obj.pk, then=attr))
lookups = obj.get_pk_lookups()
when_statements.append(When(**lookups, then=attr))
# CHANGE E
case_statement = Case(*when_statements, output_field=field)
if requires_casting:
case_statement = Cast(case_statement, output_field=field)
update_kwargs[field.attname] = case_statement
updates.append(([obj.pk for obj in batch_objs], update_kwargs))
with transaction.atomic(using=self.db, savepoint=False):
for pks, update_kwargs in updates:
self.filter(pk__in=pks).update(**update_kwargs)
bulk_update.alters_data = True

def _get_pk_condition(self, obj):
conditions = {}

pass
5 changes: 3 additions & 2 deletions compositepk-model/test/templates/app/album.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ <h2>Album List</h2>
{% endfor %}
</tbody>
</table>
{% if object_list|length > 0 %}
<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>
{% endif %}
</div>
</div>


{% endblock %}

4 changes: 4 additions & 0 deletions compositepk-model/test/templates/app/check_keys.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ <h2>{{ obj.name }}</h2>
</tr>
</thead>
<tbody>
<tr>
<td>pk</td>
<td>{{ obj.pkval }}</td>
</tr>
<tr>
<td>_meta.pk</td>
<td>{{ obj.meta.pk }}</td>
Expand Down
9 changes: 9 additions & 0 deletions compositepk-model/test/templates/app/musician.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ <h2>Musician List</h2>
{% endfor %}
</tbody>
</table>
{% if object_list|length > 0 %}
<div>
<form action="{% url 'set_profile_all' %}" method="post">
{% csrf_token %}
<input type="text" name="profile" value="">
<input type="submit" class="btn btn-primary" role="button" value="set profile to all(bulk_update)">
</form>
</div>
{% endif %}
</div>
</div>
</div>
Expand Down
46 changes: 34 additions & 12 deletions compositepk-model/test/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"""

from datetime import datetime

from django.shortcuts import render
from django.http import HttpRequest
from django.http import HttpRequest,HttpResponseRedirect
from django.views.generic.list import ListView
from django.views.generic.edit import FormView
from django.db.models import Max
from django.db import DatabaseError, InterfaceError

from test.models import (
Musician,
Album,
Expand All @@ -20,7 +22,6 @@
CompanyBranchForm,
)


################
# home
################
Expand All @@ -39,6 +40,16 @@ class MusicianListView(ListView):
template_name = 'app/musician.html'
model = Musician

def set_profile_all(request):
""" bulk_update test """
musicians = Musician.objects.all()
prof = request.POST.get('profile')
for musician in musicians:
musician.profile = prof
Musician.objects.bulk_update(musicians, ['profile',])

return HttpResponseRedirect('/musician/')


################
# Album
Expand All @@ -56,7 +67,6 @@ def get_queryset(self):
# filter pattern(2) : artist=obj
return super().get_queryset().filter(artist=musician)


class AlbumFormView(FormView):
template_name = 'app/album_create.html'
form_class = AlbumForm
Expand Down Expand Up @@ -105,6 +115,14 @@ def form_valid(self, form):
album.save()
return super().form_valid(form)

def set_5stars(request, id):
""" bulk_update test """
albums = Album.objects.filter(artist_id=id)
for album in albums:
album.num_stars = 5
Album.objects.bulk_update(albums, ['num_stars',])

return HttpResponseRedirect("/artisit/{}/album/".format(id))

################
# Company
Expand Down Expand Up @@ -247,15 +265,19 @@ def exec_test(no, key, val, memo):
################
def check_keys(request):
def make_result(name, obj):
col = obj._meta.pk.get_col(name)
result = {
'name':name,
'meta':obj._meta,
'pk':obj._meta.pk,
'pk_cls':obj._meta.pk.__class__.__name__,
'col':col,
}
return result
if obj:
col = obj._meta.pk.get_col(name)
result = {
'name':name,
'meta':obj._meta,
'pk':obj._meta.pk,
'pk_cls':obj._meta.pk.__class__.__name__,
'col':col,
'pkval':obj.pk,
}
return result
else:
return None

"""Renders the home page."""
assert isinstance(request, HttpRequest)
Expand Down

0 comments on commit 2f215b8

Please sign in to comment.