def test_update_my_field_admin_object_view(client): """ Test the "update_my_field" object view. """ actor = ddf.G(auth_models.User, is_superuser=True, is_staff=True) client.force_login(actor) my_model = ddf.G(test_models.MyModel, my_field='value') update_my_field = daf.registry.get('tests.update_my_field') view_url_name = update_my_field.interfaces['admin.object_view'].url_name url = urls.reverse(f'admin:{view_url_name}', kwargs={'pk': my_model.id}) # Try successfully performing action and test success message resp = client.post(url, data={'my_field': 'other_value'}) assert resp.status_code == 302 assert resp.url == '/admin/tests/mymodel/' resp = client.get(resp.url) assert ('Successfully performed "update my field"' in resp.content.decode()) # Successfully perfom the action, but hit the "submit and continue" # button. You should be redirected elsewhere resp = client.post(url, data={ 'my_field': 'other_value', '_continue_editing': 'True' }) assert resp.status_code == 302 assert resp.url == f'/admin/tests/mymodel/{my_model.id}/change/' assert list( test_models.MyModel.objects.values_list( 'my_field', flat=True).distinct()) == ['other_value']
def test_grant_staff_access_form_view(client): """ Test the form view for the grant_staff_access action. """ grant_staff_access = daf.registry.get('tests.grant_staff_access') detail_view_url_name = grant_staff_access.interfaces['view'].url_name url = urls.reverse(detail_view_url_name) resp = client.get(url) # We render the default display name of the action on the form assert 'Grant Staff Access' in resp.content.decode() # Try performing the action from a non superuser actor = ddf.G(auth_models.User, is_superuser=False) user = ddf.G(auth_models.User, is_staff=False) client.force_login(actor) resp = client.post(url, data={'user': user.id, 'is_staff': True}) assert 'Must be superuser in order to grant staff' in resp.content.decode() # Try successfully performing action actor.is_superuser = True actor.save() user.refresh_from_db() assert not user.is_staff resp = client.post(url, data={'user': user.id, 'is_staff': True}) user.refresh_from_db() assert user.is_staff
def test_sync_existing_objs_some_deleted(): """ Tests when some existing objects will be deleted. """ extant_obj1 = ddf.G(models.TestModel, int_field=1, float_field=1) extant_obj2 = ddf.G(models.TestModel, int_field=2, float_field=1) extant_obj3 = ddf.G(models.TestModel, int_field=3, float_field=1) pgbulk.sync( models.TestModel, [ models.TestModel(int_field=3, float_field=2), models.TestModel(int_field=4, float_field=2), models.TestModel(int_field=5, float_field=2), ], ['int_field'], ['float_field'], ) assert models.TestModel.objects.count() == 3 assert models.TestModel.objects.filter(int_field=3).exists() assert models.TestModel.objects.filter(int_field=4).exists() assert models.TestModel.objects.filter(int_field=5).exists() with pytest.raises(models.TestModel.DoesNotExist): models.TestModel.objects.get(id=extant_obj1.id) with pytest.raises(models.TestModel.DoesNotExist): models.TestModel.objects.get(id=extant_obj2.id) test_model = models.TestModel.objects.get(id=extant_obj3.id) assert test_model.int_field == 3
def test_upsert_wo_update_fields(): """ Tests bulk_upsert with no update fields. This function in turn should just do a bulk create for any models that do not already exist. """ # Create models that already exist ddf.G(models.TestModel, int_field=1, float_field=1) ddf.G(models.TestModel, int_field=2, float_field=2) # Perform a bulk_upsert with one new model pgbulk.upsert( models.TestModel, [ models.TestModel(int_field=1, float_field=3), models.TestModel(int_field=2, float_field=3), models.TestModel(int_field=3, float_field=3), ], ['int_field'], update_fields=[], ) # Three objects should now exist, but no float fields should be updated assert models.TestModel.objects.count() == 3 for test_model, expected_int_value in zip( models.TestModel.objects.order_by('int_field'), [1, 2, 3] ): assert test_model.int_field == expected_int_value assert test_model.float_field == expected_int_value
def test_complex_conditions(): """Tests complex OLD and NEW trigger conditions""" zero_to_one = ddf.G(models.TestModel, int_field=0) # Dont let intfield go from 0 -> 1 trigger = pgtrigger.Protect( when=pgtrigger.Before, operation=pgtrigger.Update, condition=pgtrigger.Q(old__int_field=0, new__int_field=1), ) with trigger.install(models.TestModel): with pytest.raises(InternalError, match='Cannot update rows'): zero_to_one.int_field = 1 zero_to_one.save() # Test a condition with a datetime field test_model = ddf.G(models.TestTrigger, int_field=0, dt_field=dt.datetime(2020, 1, 1)) trigger = pgtrigger.Protect( when=pgtrigger.Before, operation=pgtrigger.Update, condition=(pgtrigger.Q(old__int_field=0, new__int_field=1) | pgtrigger.Q(new__dt_field__lt=dt.datetime(2020, 1, 1))), ) with trigger.install(models.TestTrigger): with pytest.raises(InternalError, match='Cannot update rows'): test_model.int_field = 1 test_model.save() test_model.int_field = 2 test_model.save() with pytest.raises(InternalError, match='Cannot update rows'): test_model.dt_field = dt.datetime(2019, 1, 1) test_model.save()
def test_multiple_ignores(): """Tests multiple pgtrigger.ignore()""" deletion_protected_model1 = ddf.G(models.TestTrigger) ddf.G(models.TestTrigger) with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model1.delete() ddf.G(models.TestTrigger, field='hi!') with pytest.raises(InternalError, match='no no no!'): models.TestTrigger.objects.create(field='misc_insert') with pgtrigger.ignore('tests.TestTrigger:protect_delete'): deletion_protected_model1.delete() with pytest.raises(InternalError, match='no no no!'): models.TestTrigger.objects.create(field='misc_insert') with pgtrigger.ignore('tests.TestTrigger:protect_misc_insert'): m = models.TestTrigger.objects.create(field='misc_insert') m.delete() models.TestTrigger.objects.all().delete() assert not models.TestTrigger.objects.exists() deletion_protected_model = ddf.G(models.TestTrigger) with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model.delete() # Ignore all triggers with pgtrigger.ignore(): m = models.TestTrigger.objects.create(field='misc_insert') models.TestTrigger.objects.all().delete() assert not models.TestTrigger.objects.exists()
def test_sync_existing_objs_all_deleted(): """ Tests when there are existing objects that will all be deleted. """ extant_obj1 = ddf.G(models.TestModel, int_field=1) extant_obj2 = ddf.G(models.TestModel, int_field=2) extant_obj3 = ddf.G(models.TestModel, int_field=3) pgbulk.sync( models.TestModel, [ models.TestModel(int_field=4), models.TestModel(int_field=5), models.TestModel(int_field=6), ], ['int_field'], ['float_field'], ) assert models.TestModel.objects.count() == 3 assert models.TestModel.objects.filter(int_field=4).exists() assert models.TestModel.objects.filter(int_field=5).exists() assert models.TestModel.objects.filter(int_field=6).exists() with pytest.raises(models.TestModel.DoesNotExist): models.TestModel.objects.get(id=extant_obj1.id) with pytest.raises(models.TestModel.DoesNotExist): models.TestModel.objects.get(id=extant_obj2.id) with pytest.raises(models.TestModel.DoesNotExist): models.TestModel.objects.get(id=extant_obj3.id)
def test_grant_staff_if_same_name_object_view(client): seinfeld = ddf.G(User, first_name='Seinfeld') newman = ddf.G(User, first_name='Newman') granter = ddf.G(User, is_staff=True, is_superuser=True, first_name='Newman') client.force_login(granter) # Test same name url = urls.reverse('grant_staff_access_if_same_name_object', kwargs={'pk': newman.id}) assert not newman.is_staff resp = client.post(url, data={'is_staff': True, 'source': 'good'}) assert resp.status_code == 302 newman.refresh_from_db() assert newman.is_staff # Test mismatch name url = urls.reverse('grant_staff_access_if_same_name_object', kwargs={'pk': seinfeld.id}) assert not seinfeld.is_staff resp = client.post(url, data={'is_staff': True, 'source': 'good'}) assert resp.status_code == 404 seinfeld.refresh_from_db() assert not seinfeld.is_staff
def test_sync_w_char_pk(): """ Tests with a model that has a char pk. """ extant_obj1 = ddf.G(models.TestPkChar, my_key='1', char_field='1') extant_obj2 = ddf.G(models.TestPkChar, my_key='2', char_field='1') extant_obj3 = ddf.G(models.TestPkChar, my_key='3', char_field='1') pgbulk.sync( models.TestPkChar, [ models.TestPkChar(my_key='3', char_field='2'), models.TestPkChar(my_key='4', char_field='2'), models.TestPkChar(my_key='5', char_field='2'), ], ['my_key'], ['char_field'], ) assert models.TestPkChar.objects.count() == 3 assert models.TestPkChar.objects.filter(my_key='3').exists() assert models.TestPkChar.objects.filter(my_key='4').exists() assert models.TestPkChar.objects.filter(my_key='5').exists() with pytest.raises(models.TestPkChar.DoesNotExist): models.TestPkChar.objects.get(pk=extant_obj1.pk) with pytest.raises(models.TestPkChar.DoesNotExist): models.TestPkChar.objects.get(pk=extant_obj2.pk) test_model = models.TestPkChar.objects.get(pk=extant_obj3.pk) assert test_model.char_field == '2'
def test_grant_staff_if_same_name_objects_view(client): seinfelds = ddf.G(User, n=2, first_name='Seinfeld') newmans = ddf.G(User, n=2, first_name='Newman') granter = ddf.G(User, is_staff=True, is_superuser=True, first_name='Newman') client.force_login(granter) # Test a "GET" when one PK is in queryset, and the other is not url = urls.reverse('grant_staff_access_if_same_name_objects') url += f'?pk={seinfelds[0].id}&pk={newmans[0].id}' resp = client.get(url) assert resp.status_code == 404 # And now, when both PKs are in the queryset url = urls.reverse('grant_staff_access_if_same_name_objects') url += f'?pk={newmans[0].id}&pk={newmans[1].id}' resp = client.get(url) assert resp.status_code == 200 # Test a valid post for newman in newmans: newman.refresh_from_db() assert not newman.is_staff resp = client.post(url, data={'is_staff': True, 'source': 'good'}) for newman in newmans: newman.refresh_from_db() assert newman.is_staff
def test_update_objs_two_fields_to_update(): """ Tests when objects are given to bulk update with two fields to update. """ test_obj_1 = ddf.G(models.TestModel, int_field=1, float_field=1.0) test_obj_2 = ddf.G(models.TestModel, int_field=2, float_field=2.0) # Change the int and float fields on the models test_obj_1.int_field = 3 test_obj_2.int_field = 4 test_obj_1.float_field = 3.0 test_obj_2.float_field = 4.0 # Do a bulk update with the int fields pgbulk.update( models.TestModel, [test_obj_1, test_obj_2], ['int_field', 'float_field'], ) # The test objects int fields should be untouched test_obj_1 = models.TestModel.objects.get(id=test_obj_1.id) test_obj_2 = models.TestModel.objects.get(id=test_obj_2.id) assert test_obj_1.int_field == 3 assert test_obj_2.int_field == 4 # The float fields should be updated assert test_obj_1.float_field == 3.0 assert test_obj_2.float_field == 4.0
def test_qset(qset_kwargs, expected_error): """Tests the djarg.qset utility for coercing querysets""" with expected_error: @arg.defaults(users=djarg.qset( 'users', **qset_kwargs).prefetch_related('groups')) def get_user_groups(users): return {group for user in users for group in user.groups.all()} users = ddf.G(auth_models.User, n=3) groups = ddf.G(auth_models.Group, n=3) users[0].groups.add(groups[0], groups[1]) users[1].groups.add(groups[1], groups[2]) users[2].groups.add(groups[2]) assert get_user_groups(None) == set() assert get_user_groups([]) == set() assert get_user_groups([users[0].id]) == {groups[0], groups[1]} assert get_user_groups([users[0].id, users[1].id]) == set(groups) assert get_user_groups([users[0], users[1]]) == set(groups) assert get_user_groups(users[0]) == {groups[0], groups[1]} assert get_user_groups(auth_models.User.objects.filter( id=users[0].id)) == {groups[0], groups[1]}
def test_grant_staff_cond_wizard_view(client): user = ddf.G(User, is_staff=True) granter = ddf.G(User, is_staff=True, is_superuser=True) url = urls.reverse('grant_staff_access_cond_wizard') resp = client.get(url) html = resp.content.decode() # Verify the custom help text was rendered on our user field assert 'Help text' in html # Post no data. We should have a "field is required" errors resp = client.post(url, data={'grant_staff_cond_wizard_view-current_step': '0'}) assert resp.status_code == 200 html = resp.content.decode() assert 'This field is required' in html # Post data and go to the next conditional step. # The conditional step is based on the value of "is_staff" resp = client.post( url, data={ 'grant_staff_cond_wizard_view-current_step': '0', '0-user': user.id, '0-granter': granter.id, '0-is_staff': True, }, ) assert resp.status_code == 200 html = resp.content.decode() assert 'Staff is true!' in html # Try the other conditional step when the staff value is false resp = client.post( url, data={ 'grant_staff_cond_wizard_view-current_step': '0', '0-user': user.id, '0-granter': granter.id, '0-is_staff': False, }, ) assert resp.status_code == 200 html = resp.content.decode() assert 'Staff is false!' in html # Post the granter. Using the same user will result in a validation # error resp = client.post( url, data={ 'grant_staff_cond_wizard_view-current_step': '2', '2-source': 'source', }, ) assert resp.status_code == 302 user.refresh_from_db() assert not user.is_staff
def test_simple_dump_ls_restore(tmpdir, capsys, settings): """ Tests a simple dump, ls, and restore, asserting that a user created after a dump is deleted upon restore """ db_name = settings.DATABASES['default']['NAME'] settings.PGCLONE_STORAGE_LOCATION = tmpdir.strpath call_command('pgclone', 'ls') assert capsys.readouterr().out == '' with pytest.raises(RuntimeError): call_command('pgclone', 'restore', db_name) ddf.G('auth.User') call_command('pgclone', 'dump') call_command('pgclone', 'ls') assert capsys.readouterr().out == ( f'{db_name}/2020_07_01_00_00_00_000000.default.dump\n' ) ddf.G('auth.User') assert User.objects.count() == 2 call_command('pgclone', 'restore', db_name) connection.connect() assert User.objects.count() == 1 call_command( 'pgclone', 'restore', f'{db_name}/2020_07_01_00_00_00_000000.default.dump', ) connection.connect() assert User.objects.count() == 1 # Do some basic error assertions with pytest.raises(pgclone.exceptions.ConfigurationError): call_command('pgclone', 'dump', '-c bad_config_name') with pytest.raises(pgclone.exceptions.ConfigurationError): call_command('pgclone', 'restore', db_name, '-c bad_config_name') # Try restoring with custom swap hooks call_command('pgclone', 'restore', db_name, '--pre-swap-hook', 'migrate') connection.connect() assert User.objects.count() == 1 # Dump and restore while ignoring the user table with freezegun.freeze_time('2020-07-02'): call_command('pgclone', 'dump', '--exclude-model', 'auth.User') assert User.objects.count() == 1 call_command('pgclone', 'restore', db_name) connection.connect() assert not User.objects.exists()
def test_soft_delete(): """ Verifies the SoftDelete test model has the "is_active" flag set to false """ soft_delete = ddf.G(models.SoftDelete, is_active=True) ddf.G(models.FkToSoftDelete, ref=soft_delete) soft_delete.delete() assert not models.SoftDelete.objects.get().is_active assert not models.FkToSoftDelete.objects.exists()
def test_grant_staff_objects_view(client): users = ddf.G(User, n=2, is_staff=True) granter = ddf.G(User, is_staff=True, is_superuser=True, username='******') client.force_login(granter) # Test a 404 where no PKs are supplied url = urls.reverse('grant_staff_access_objects') resp = client.get(url) assert resp.status_code == 404 # Test a 404 where no bad PKs are supplied url = urls.reverse('grant_staff_access_objects') url += f'?pk={users[0].id}&pk=0' resp = client.get(url) assert resp.status_code == 404 # Test a proper page load url = urls.reverse('grant_staff_access_objects') url += f'?pk={users[0].id}&pk={users[1].id}' resp = client.get(url) assert resp.status_code == 200 # Verify runtime errors don't result in success messages users[0].username = '******' users[0].save() resp = client.post(url, data={'is_staff': False, 'source': 'good'}) assert 'Test runtime error' in resp.content.decode() assert 'GRANT successfully' not in resp.content.decode() # Test a valid post users[0].username = '******' users[0].save() for user in users: assert user.is_staff resp = client.post(url, data={'is_staff': False, 'source': 'good'}) assert resp.status_code == 302 for user in users: user.refresh_from_db() assert not user.is_staff # Verify that the valid post results in a success message resp = client.get(url) assert ('GRANT successfully granted staff access to users.' in resp.content.decode()) # Make both users have invalid usernames so we can check bulk errors users[0].username = '******' users[0].save() users[1].username = '******' users[1].save() resp = client.post(url, data={'is_staff': False, 'source': 'good'}) assert resp.status_code == 200 content = resp.content.decode() assert 'bad_user1: Bad username' in content assert 'bad_user2: Bad username' in content
def test_grant_staff_access_drf_action(api_client, mocker): """Run the GrantStaffAccess DRF action""" actor = ddf.G(auth_models.User, is_superuser=True) actor.set_password('password') actor.save() user = ddf.G(auth_models.User, is_staff=False) grant_staff_access = daf.registry.get('tests.grant_staff_access') detail_view_url_name = ( 'user-' + grant_staff_access.interfaces['rest_framework.detail_action'].url_name) url = urls.reverse(detail_view_url_name, kwargs={'pk': user.id}) api_client.force_login(actor) # Perform a run where form validation fails resp = api_client.post(url, data={'date_granted': 'invalid'}) assert resp.status_code == 400 assert resp.json() == {'date_granted': ['Enter a valid date/time.']} # Perform a successful run resp = api_client.post(url, data={'is_staff': True}) assert resp.status_code == 200 assert resp.json() == { 'email': user.email, 'id': user.id, 'username': user.username, 'is_staff': True, } # Make sure refetching for serialization works mocker.patch.object(GrantStaffAccessObjectDRFAction, 'refetch_for_serialization', False) resp = api_client.post(url, data={'is_staff': True}) assert resp.json() == { 'email': user.email, 'id': user.id, 'username': user.username, 'is_staff': True, } # Make sure refetching for serialization works even without using # a parametrized wrapper mocker.patch.object( daf.actions.ObjectAction, 'wrapper', arg.defaults(user=arg.val('object')), ) resp = api_client.post(url, data={'is_staff': True}) assert resp.json() == { 'email': user.email, 'id': user.id, 'username': user.username, 'is_staff': True, }
def test_trigger_conditions(): """Tests triggers with custom conditions""" test_model = ddf.G(models.TestTrigger) # Protect against inserts only when "field" is "hello" trigger = pgtrigger.Trigger( name='test_condition1', when=pgtrigger.Before, operation=pgtrigger.Insert, func="RAISE EXCEPTION 'no no no!';", condition=pgtrigger.Q(new__field='hello'), ) with trigger.install(test_model): ddf.G(models.TestTrigger, field='hi!') with pytest.raises(InternalError, match='no no no!'): models.TestTrigger.objects.create(field='hello') # Protect updates where nothing is actually updated trigger = pgtrigger.Trigger( name='test_condition2', when=pgtrigger.Before, operation=pgtrigger.Update, func="RAISE EXCEPTION 'no no no!';", condition=pgtrigger.Condition('OLD.* IS NOT DISTINCT FROM NEW.*'), ) with trigger.install(test_model): test_model.int_field = test_model.int_field + 1 test_model.save() # Saving the same fields again will cause an error with pytest.raises(InternalError, match='no no no!'): test_model.save() # Make a model readonly when the int_field is 0 read_only = ddf.G(models.TestModel, int_field=0) non_read_only = ddf.G(models.TestModel, int_field=1) trigger = pgtrigger.Trigger( name='test_condition3', when=pgtrigger.Before, operation=pgtrigger.Update | pgtrigger.Delete, func="RAISE EXCEPTION 'no no no!';", condition=pgtrigger.Q(old__int_field=0), ) with trigger.install(models.TestModel): with pytest.raises(InternalError, match='no no no!'): read_only.save() with pytest.raises(InternalError, match='no no no!'): read_only.delete() non_read_only.save() non_read_only.delete()
def test_grant_staff_access_function(): """Obtain the GrantStaffAccess action from the registry and run it""" user = ddf.G(auth_models.User, is_staff=False) actor = ddf.G(auth_models.User, is_superuser=True) grant_staff_access = daf.registry.get('tests.grant_staff_access') user = grant_staff_access.func.func(user=user, actor=actor, is_staff=True) # Verify the return value and the value persisted to the database assert user.is_staff user.refresh_from_db() assert user.is_staff
def test_fk_cascading(mocker): """ Makes a snapshot and then removes a foreign key. Since django will set this foreign key to null with a cascading operation, the history tracking should also capture this and preserve the original foreign key value. """ orig_user = ddf.G('auth.User') tracking = ddf.G(test_models.SnapshotModel, fk_field=orig_user) orig_user_id = orig_user.id assert orig_user is not None assert list( tracking.snapshot.order_by('pgh_id').values( 'fk_field_id', 'pgh_obj_id' ) ) == [{'fk_field_id': tracking.fk_field_id, 'pgh_obj_id': tracking.id}] assert list( tracking.custom_related_name.order_by('pgh_id').values( 'fk_field_id', 'pgh_obj_id' ) ) == [{'fk_field_id': tracking.fk_field_id, 'pgh_obj_id': tracking.id}] original_custom_pgh_id = tracking.custom_related_name.get().pk # Deleting the user should set the user to None in the tracking model orig_user.delete() tracking.refresh_from_db() assert tracking.fk_field_id is None # The tracked history should retain the original user assert list( tracking.snapshot.order_by('pgh_id').values( 'fk_field_id', 'pgh_obj_id' ) ) == [ {'fk_field_id': orig_user_id, 'pgh_obj_id': tracking.id}, {'fk_field_id': None, 'pgh_obj_id': tracking.id}, ] # The custom tracking model is set to cascade delete whenever users # are deleted. The original tracking row should be gone assert not tracking.custom_related_name.filter( pk=original_custom_pgh_id ).exists() # A new tracking row is still created for the new SnapshotModel that has # its user value set to None because of the cascade assert list( tracking.custom_related_name.order_by('pgh_id').values( 'fk_field_id', 'pgh_obj_id' ) ) == [{'fk_field_id': None, 'pgh_obj_id': tracking.id}]
def test_custom_trigger_definitions(): """Test a variety of custom trigger definitions""" test_model = ddf.G(models.TestTrigger) # Protect against inserts or updates # Note: Although we could use the "protect" trigger for this, # we manually provide the trigger code to test manual declarations trigger = pgtrigger.Trigger( name='test_custom_definition1', when=pgtrigger.Before, operation=pgtrigger.Insert | pgtrigger.Update, func="RAISE EXCEPTION 'no no no!';", ) with trigger.install(test_model): # Inserts and updates are no longer available with pytest.raises(InternalError, match='no no no!'): models.TestTrigger.objects.create() with pytest.raises(InternalError, match='no no no!'): test_model.save() # Inserts and updates should work again ddf.G(models.TestTrigger) test_model.save() # Protect updates of a single column trigger = pgtrigger.Trigger( name='test_custom_definition2', when=pgtrigger.Before, operation=pgtrigger.UpdateOf('int_field'), func="RAISE EXCEPTION 'no no no!';", ) with trigger.install(models.TestTrigger): # "field" should be able to be updated, but other_field should not test_model.save(update_fields=['field']) with pytest.raises(InternalError, match='no no no!'): test_model.save(update_fields=['int_field']) # Protect statement-level creates trigger = pgtrigger.Trigger( name='test_custom_definition3', level=pgtrigger.Statement, when=pgtrigger.Before, operation=pgtrigger.Update, func="RAISE EXCEPTION 'bad statement!';", ) with trigger.install(models.TestTrigger): with pytest.raises(InternalError, match='bad statement!'): test_model.save()
def test_trigger_management(mocker): """Verifies dropping and recreating triggers works""" deletion_protected_model = ddf.G(models.TestTrigger) # Triggers should be installed initially with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model.delete() # Deactivate triggers. Deletions should happen without issue. # Note: run twice for idempotency checks pgtrigger.disable() pgtrigger.disable() deletion_protected_model.delete() # Reactivate triggers. Deletions should be protected pgtrigger.enable() pgtrigger.enable() deletion_protected_model = ddf.G(models.TestTrigger) with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model.delete() # Do the same tests again, except this time uninstall and reinstall # triggers pgtrigger.uninstall() pgtrigger.uninstall() deletion_protected_model.delete() # Reactivate triggers. Deletions should be protected pgtrigger.install() pgtrigger.install() deletion_protected_model = ddf.G(models.TestTrigger) with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model.delete() # Pruning triggers should do nothing at the moment pgtrigger.prune() pgtrigger.prune() with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model.delete() # However, changing the trigger name will cause the old triggers to # be pruned mocker.patch( 'pgtrigger.Protect.name', new_callable=mocker.PropertyMock, return_value='hi', ) pgtrigger.prune() pgtrigger.prune() deletion_protected_model.delete()
def test_basic_ignore(): """Verify basic dynamic ignore functionality""" deletion_protected_model = ddf.G(models.TestTrigger) with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model.delete() with pgtrigger.ignore('tests.TestTrigger:protect_delete'): deletion_protected_model.delete() assert not models.TestTrigger.objects.exists() deletion_protected_model = ddf.G(models.TestTrigger) with pytest.raises(InternalError, match='Cannot delete rows'): deletion_protected_model.delete()
def test_update_my_field_admin_objects_view(client): """ Test the "update_my_field" objects view. Test the flow of dismissing failing objects """ actor = ddf.G(auth_models.User, is_superuser=True, is_staff=True) client.force_login(actor) my_models = ddf.G(test_models.MyModel, n=3, my_field='value') update_my_field = daf.registry.get('tests.update_my_field') view_url_name = update_my_field.interfaces['admin.objects_view'].url_name url = urls.reverse(f'admin:{view_url_name}') # No objects should result in 404 assert client.get(url).status_code == 404 url += f'?pk={my_models[0].id}&pk={my_models[1].id}&pk={my_models[2].id}' resp = client.get(url) assert resp.status_code == 200 assert 'Update My Field' in resp.content.decode() assert ' - Three My Models' in resp.content.decode() # Make all objects fail test_models.MyModel.objects.update(my_field='aaa') resp = client.post(url, data={'my_field': 'other_value'}) assert resp.status_code == 200 content = resp.content.decode() assert ( f'{my_models[0]} - "my_field" is "aaa". Cannot update' in content) assert ( f'{my_models[1]} - "my_field" is "aaa". Cannot update' in content) assert ( f'{my_models[2]} - "my_field" is "aaa". Cannot update' in content) # Try successfully performing action and test success message test_models.MyModel.objects.update(my_field='valid') resp = client.post(url, data={'my_field': 'other_value'}) assert resp.status_code == 302 resp = client.get(resp.url) assert ( 'Successfully performed "update my field" on three my models' in resp.content.decode()) assert list( test_models.MyModel.objects.values_list( 'my_field', flat=True).distinct()) == ['other_value']
def test_unique_field_tracking(): """Verifies tracking works on models with unique constraints""" pk_model = ddf.G(test_models.CustomModel) unique_model = ddf.G( test_models.UniqueConstraintModel, my_one_to_one=pk_model, my_char_field='1', my_int_field1=1, my_int_field2=2, ) unique_model.my_int_field2 = 1 unique_model.save() unique_model.my_int_field2 = 2 unique_model.save() assert unique_model.snapshot.count() == 3
def test_update_ints_to_null(): """ Tests updating an int field to a null field. """ test_obj_1 = ddf.G(models.TestModel, int_field=1, float_field=2) test_obj_2 = ddf.G(models.TestModel, int_field=2, float_field=3) test_obj_1.int_field = None test_obj_2.int_field = None pgbulk.update(models.TestModel, [test_obj_1, test_obj_2], ['int_field']) test_obj_1 = models.TestModel.objects.get(id=test_obj_1.id) test_obj_2 = models.TestModel.objects.get(id=test_obj_2.id) assert test_obj_1.int_field is None assert test_obj_2.int_field is None
def test_update_chars_to_null(): """ Tests updating a char field to a null field. """ test_obj_1 = ddf.G(models.TestModel, int_field=1, char_field='2') test_obj_2 = ddf.G(models.TestModel, int_field=2, char_field='3') test_obj_1.char_field = None test_obj_2.char_field = None pgbulk.update(models.TestModel, [test_obj_1, test_obj_2], ['char_field']) test_obj_1 = models.TestModel.objects.get(id=test_obj_1.id) test_obj_2 = models.TestModel.objects.get(id=test_obj_2.id) assert test_obj_1.char_field is None assert test_obj_2.char_field is None
def test_lazy_load_qset(): """ Verifies the qset utility can be given a args.Lazy qset argument """ def _get_qset(username): return auth_models.User.objects.filter(username=username) @arg.defaults(users=djarg.qset('users', qset=arg.func(_get_qset))) def get_user(users): return users.get() user1 = ddf.G(auth_models.User) user2 = ddf.G(auth_models.User) assert get_user(users=[user1, user2], username=user1.username) == user1
def test_create_event(): """ Verifies events can be created manually and are linked with proper context """ m = ddf.G('tests.EventModel') with pytest.raises(ValueError, match='not a registered event'): pghistory.create_event(m, label='invalid_event') event = pghistory.create_event(m, label='manual_event') assert event.pgh_label == 'manual_event' assert event.dt_field == m.dt_field assert event.int_field == m.int_field assert event.pgh_context is None event = pghistory.create_event(m, label='no_pgh_obj_manual_event') assert event.pgh_label == 'no_pgh_obj_manual_event' assert event.dt_field == m.dt_field assert event.int_field == m.int_field assert event.pgh_context is None # Context should be added properly with pghistory.context(hello='world') as ctx: event = pghistory.create_event(m, label='manual_event') assert event.pgh_label == 'manual_event' assert event.dt_field == m.dt_field assert event.int_field == m.int_field assert event.pgh_context.id == ctx.id assert event.pgh_context.metadata == {'hello': 'world'}
def test_dt_field_snapshot_tracking(mocker): """ Tests the snapshot trigger for the dt_field tracker. """ tracking = ddf.G( test_models.SnapshotModel, dt_field=dt.datetime(2020, 1, 1, tzinfo=dt.timezone.utc), ) assert tracking.dt_field_snapshot.exists() tracking.dt_field = dt.datetime(2019, 1, 1, tzinfo=dt.timezone.utc) tracking.save() # Do an empty update to make sure extra snapshot aren't tracked tracking.save() assert list(tracking.dt_field_snapshot.order_by('pgh_id').values()) == [ { 'pgh_id': mocker.ANY, 'pgh_label': 'dt_field_snapshot', 'dt_field': dt.datetime(2020, 1, 1, tzinfo=dt.timezone.utc), 'pgh_obj_id': tracking.id, 'pgh_created_at': mocker.ANY, 'pgh_context_id': None, }, { 'pgh_id': mocker.ANY, 'pgh_label': 'dt_field_snapshot', 'dt_field': dt.datetime(2019, 1, 1, tzinfo=dt.timezone.utc), 'pgh_obj_id': tracking.id, 'pgh_created_at': mocker.ANY, 'pgh_context_id': None, }, ]