Ejemplo n.º 1
0
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']
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
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()
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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'
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
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]}
Ejemplo n.º 13
0
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()
Ejemplo n.º 15
0
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()
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
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,
    }
Ejemplo n.º 18
0
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()
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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}]
Ejemplo n.º 21
0
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()
Ejemplo n.º 22
0
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()
Ejemplo n.º 23
0
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()
Ejemplo n.º 24
0
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']
Ejemplo n.º 25
0
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
Ejemplo n.º 26
0
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
Ejemplo n.º 27
0
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
Ejemplo n.º 28
0
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
Ejemplo n.º 29
0
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'}
Ejemplo n.º 30
0
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,
        },
    ]