Skip to content

Commit

Permalink
Feat: policy can be designated as a template (#578)
Browse files Browse the repository at this point in the history
  • Loading branch information
orzubalsky committed Dec 27, 2022
1 parent 6729fa8 commit 6d6dd89
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 18 deletions.
18 changes: 18 additions & 0 deletions policykit/policyengine/migrations/0016_policy_is_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.2 on 2022-12-12 16:12

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('policyengine', '0015_auto_20221120_1740'),
]

operations = [
migrations.AddField(
model_name='policy',
name='is_template',
field=models.BooleanField(default=False),
),
]
19 changes: 19 additions & 0 deletions policykit/policyengine/migrations/0017_alter_policy_community.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.2 on 2022-12-12 16:18

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('policyengine', '0016_policy_is_template'),
]

operations = [
migrations.AlterField(
model_name='policy',
name='community',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='policyengine.community'),
),
]
46 changes: 38 additions & 8 deletions policykit/policyengine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ class Policy(models.Model):
fail = models.TextField(blank=True, default='')
"""The fail code of the policy."""

community = models.ForeignKey(Community, models.CASCADE)
community = models.ForeignKey(Community, models.CASCADE, null=True)
"""The community which the policy belongs to."""

action_types = models.ManyToManyField(ActionType)
Expand All @@ -838,6 +838,9 @@ class Policy(models.Model):
is_active = models.BooleanField(default=True)
"""True if the policy is active. Default is True."""

is_template = models.BooleanField(default=False)
"""True if the policy can be used as a template for other policies. Default is False."""

modified_at = models.DateTimeField(auto_now=True)
"""Datetime object representing the last time the policy was modified."""

Expand All @@ -861,22 +864,27 @@ def is_bundled(self):
"""True if the policy is part of a bundle"""
return self.member_of_bundle.count() > 0

def copy(self, community = None):
"""Make a copy of the policy object and assign to a new community"""
if not community:
raise Exception("Community object must be passed")
def save(self, *args, **kwargs):
if not self.is_template and self.community is None:
raise ValidationError('Non template policies must have a community')

super(Policy, self).save(*args, **kwargs)

def copy_as_template(self):
"""Make a copy of the policy object and designate it as a template"""

from copy import deepcopy

# Make a copy of the whole objet
# Make a copy of the whole object
new_policy = deepcopy(self)

# Generate a new id
new_policy.pk = None

# Assign copy to another community
new_policy.community = community
# Treat new policy as a template as to not requiring a community relationship yet
new_policy.is_template = True

# Save new policy
new_policy.save()

# Copy ActionType relationships
Expand All @@ -895,6 +903,28 @@ def copy(self, community = None):

return new_policy

def copy_to_community(self, community = None):
"""Make a copy of the policy object and assign to a new community"""

if not self.is_template:
raise Exception("Policy is not a template")

if not community:
raise Exception("Community object must be passed")

# Generate a copy of the policy
new_policy = self.copy_as_template()

# Designate policy as not-a-template
new_policy.is_template = False

# Assign copy to another community
new_policy.community = community

# Save
new_policy.save()

return new_policy

class UserVote(models.Model):
"""UserVote"""
Expand Down
36 changes: 26 additions & 10 deletions policykit/tests/test_clone_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def setUp(self):
self.client.force_login(user=self.user, backend="integrations.slack.auth_backends.SlackBackend")

# Create a policy to use as a source
self.policy_source = Policy.objects.create(**TestUtils.ALL_ACTIONS_PASS, kind=Policy.TRIGGER, community=self.community_source)
self.policy_source = Policy.objects.create(**TestUtils.ALL_ACTIONS_PASS, kind=Policy.TRIGGER, community=self.community_source, is_template=True)

# Add an action type to source policy
self.action_type = ActionType.objects.get_or_create(codename="slackpostmessage")[0]
Expand All @@ -28,19 +28,14 @@ def setUp(self):
self.slack_community_source, user2 = TestUtils.create_slack_community_and_user(team_id="target", username="user2")
self.community_target = self.slack_community_source.community

def test_no_community_raises_exception(self):
# Check that an exception is raied when copy() is used without a community parameter
with self.assertRaisesRegexp(Exception, "Community object must be passed"):
self.policy_source.copy()

def test_copy_policy(self):
new_policy = self.policy_source.copy(self.community_target)
def test_copy_policy_as_template(self):
new_policy = self.policy_source.copy_as_template()

# Check that policy copy was created
self.assertNotEqual(new_policy.pk, self.policy_source.pk)

# Check that policy copy is related the target community
self.assertEqual(new_policy.community, self.community_target)
# Check that new policy is a template
self.assertTrue(new_policy.is_template)

# Check that policy copy has the same values as original
for fieldname in [ "kind", "filter", "initialize", "check", "notify", "success", "fail", "name", "description", "is_active" ]:
Expand All @@ -65,3 +60,24 @@ def test_copy_policy(self):

# Check that policy variable value is set to the default value
self.assertEqual(new_variable.value, self.policy_variable_source.default_value)

def test_no_community_raises_exception(self):
# Check that an exception is raied when copy_to_community() is used without a community parameter
with self.assertRaisesRegexp(Exception, "Community object must be passed"):
self.policy_source.copy_to_community()

def test_not_template_policy_raises_exception(self):
# Check that an exception is raied when copy_to_community() is used on a policy that is not designated as a template
self.policy_source.is_template = False

with self.assertRaisesRegexp(Exception, "Policy is not a template"):
self.policy_source.copy_to_community(community=self.community_target)

def test_copy_policy_to_community(self):
new_policy = self.policy_source.copy_to_community(self.community_target)

# Check that policy copy is related the target community
self.assertEqual(new_policy.community, self.community_target)

# Check that new policy is not a template
self.assertFalse(new_policy.is_template)
28 changes: 28 additions & 0 deletions policykit/tests/test_policy_community_required.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.test import TestCase
from policyengine.models import Policy

import tests.utils as TestUtils


class PolicyCommunityTests(TestCase):
"""
Test policy create/save as it relates to the community field
"""
def test_saving_policy_without_community_raises_exception(self):
# Check that an exception is raied when a non-template policy is saved without a community
with self.assertRaisesRegexp(Exception, "Non template policies must have a community"):
Policy.objects.create(
**TestUtils.ALL_ACTIONS_PROPOSED,
kind=Policy.CONSTITUTION,
is_template=False
)

def test_saving_template_policy_without_community(self):
# Check that a template policy can be created without a community
policy = Policy.objects.create(
**TestUtils.ALL_ACTIONS_PROPOSED,
kind=Policy.CONSTITUTION,
is_template=True
)

self.assertEqual(policy.pk, 1)

0 comments on commit 6d6dd89

Please sign in to comment.