def form_valid(self, formset): formset.save(commit=False) fy_has_changed = { } # use dict to avoid recording multiple occurences of the same # FY being affected for form in formset: if 'fy' in form.changed_data: fy_id = form.initial.get("fy") fy_queryset = form.fields["fy"]._queryset fy = next(fy for fy in fy_queryset if fy.pk == fy_id) fy_has_changed[fy_id] = fy # we need to rollback now to the earliest of the financial years which has changed # do this before we make changes to the period objects and FY objects fys = [fy for fy in fy_has_changed.values()] if fys: earliest_fy_affected = min(fys, key=lambda fy: fy.financial_year) if earliest_fy_affected: # because user may not in fact change anything # if the next year after the earliest affected does not exist no exception is thrown # the db query just won't delete anything NominalTransaction.objects.rollback_fy( earliest_fy_affected.financial_year + 1) # now all the bfs have been deleted we can change the period objects instances = [form.instance for form in formset] fy_period_counts = {} for fy_id, periods in groupby(instances, key=lambda p: p.fy_id): fy_period_counts[fy_id] = len(list(periods)) fys = FinancialYear.objects.all() for fy in fys: fy.number_of_periods = fy_period_counts[fy.pk] # no point auditing this FinancialYear.objects.bulk_update(fys, ["number_of_periods"]) bulk_update_with_history(instances, Period, ["period", "fy_and_period", "fy"]) return HttpResponseRedirect(self.get_success_url())
def test_bulk_update_history_num_queries_is_two(self): with self.assertNumQueries(2): bulk_update_with_history( self.data, Poll, fields=["question"], )
def post(self, request, *args, **kwargs): polls = Poll.objects.order_by("pub_date") for i, poll in enumerate(polls): poll.question = str(i) bulk_update_with_history(polls, fields=["question"], model=Poll) return HttpResponse(status=201)
def test_bulk_update_history_wrong_manager(self): with self.assertRaises(AlternativeManagerError): bulk_update_with_history( self.data, PollWithAlternativeManager, fields=["question"], manager=Poll.objects, )
def test_bulk_update_history_with_batch_size(self): bulk_update_with_history(self.data, Poll, fields=["question"], batch_size=2) self.assertEqual(Poll.objects.count(), 5) self.assertEqual(Poll.history.filter(history_type="~").count(), 5)
def bulk_update(cls, license_objects, field_names, batch_size=LICENSE_BULK_OPERATION_BATCH_SIZE): """ django-simple-history functions by saving history using a post_save signal every time that an object with history is saved. However, for certain bulk operations, such as bulk_create, bulk_update, and queryset updates, signals are not sent, and the history is not saved automatically. However, django-simple-history provides utility functions to work around this. https://django-simple-history.readthedocs.io/en/2.12.0/common_issues.html#bulk-creating-and-queryset-updating """ bulk_update_with_history(license_objects, cls, field_names, batch_size=batch_size)
def test_bulk_update_history_on_model_without_history_raises_error(self): self.data = [ Place(id=1, name="Place 1"), Place(id=2, name="Place 2"), Place(id=3, name="Place 3"), ] Place.objects.bulk_create(self.data) self.data[0].name = "test" with self.assertRaises(NotHistoricalModelError): bulk_update_with_history(self.data, Place, fields=["name"])
def test_bulk_update_history(self): bulk_update_with_history( self.data, Poll, fields=["question"], ) self.assertEqual(Poll.objects.count(), 5) self.assertEqual(Poll.objects.get(id=4).question, "Updated question") self.assertEqual(Poll.history.count(), 10) self.assertEqual(Poll.history.filter(history_type="~").count(), 5)
def test_bulk_update_history_with_default_date(self): date = datetime(2020, 7, 1) bulk_update_with_history(self.data, Poll, fields=["question"], default_date=date) self.assertTrue( all([ history.history_date == date for history in Poll.history.filter(history_type="~") ]))
def post(self, request, *args, **kwargs): default_user = CustomUser.objects.create_superuser( "test_user", "*****@*****.**", "pass" ) polls = Poll.objects.all() for i, poll in enumerate(polls): poll.question = str(i) bulk_update_with_history( polls, fields=["question"], model=Poll, default_user=default_user ) return HttpResponse(status=201)
def test_bulk_update_history_with_default_change_reason(self): bulk_update_with_history( self.data, Poll, fields=["question"], default_change_reason="my change reason", ) self.assertTrue( all([ history.history_change_reason == "my change reason" for history in Poll.history.filter(history_type="~") ]))
def test_bulk_update_history_with_default_user(self): user = User.objects.create_user("tester", "*****@*****.**") bulk_update_with_history(self.data, Poll, fields=["question"], default_user=user) self.assertTrue( all([ history.history_user == user for history in Poll.history.filter(history_type="~") ]))
def __update_old_products_instance(self, df): products = Product.objects.filter( FK_Shop=self.shop, barcode__in=df[self.product_barcode_field].to_list()) update_list = [] for product in products: for field in self.update_fields: if field in df.columns: self.__set_product_attribute(product, field, df) update_list.append(product) bulk_update_with_history(update_list, Product, self.update_fields, batch_size=500, default_user=self.shop.FK_ShopManager, default_change_reason=f'tag:{self.shop.ID}') return update_list
def test_bulk_update_history_default_manager(self): self.data[3].question = "Updated question" bulk_update_with_history( self.data, PollWithAlternativeManager, fields=["question"], ) self.assertEqual(PollWithAlternativeManager.all_objects.count(), 5) self.assertEqual( PollWithAlternativeManager.all_objects.get(id=4).question, "Updated question", ) self.assertEqual(PollWithAlternativeManager.history.count(), 10) self.assertEqual( PollWithAlternativeManager.history.filter( history_type="~").count(), 5)
def audited_bulk_update(self, objs, fields=None, batch_size=None, user=None): if not fields: fields = self.model.fields_to_update() return bulk_update_with_history(objs, self.model, fields, batch_size=batch_size, default_user=user)
def test_num_queries_when_batch_size_is_less_than_total(self): with self.assertNumQueries(6): bulk_update_with_history(self.data, Poll, fields=["question"], batch_size=2)