Skip to content

Commit

Permalink
Create command to remove unused data by checking existing code
Browse files Browse the repository at this point in the history
  • Loading branch information
cristianowa committed Dec 11, 2023
1 parent 63444d6 commit ea82d0c
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 1 deletion.
11 changes: 11 additions & 0 deletions docs/usage/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,14 @@ Use ``manage.py`` to delete a batch of flags, switches, and/or samples::
$ ./manage.py waffle_delete --switches switch_name_0 switch_name_1 --flags flag_name_0 flag_name_1 --samples sample_name_0 sample_name_1

Pass a list of switch, flag, or sample names to the command as keyword arguments and they will be deleted from the database.

Deleting Unused Data
====================

Use ``manage.py`` to delete all flags, switches, and/or samples that are not used in any flag, switch, or sample objects::

$ ./manage.py waffle_delete_unused --switches --flags --samples

To by pass the confirmation prompt, use the ``--noinput`` flag::

$ ./manage.py waffle_delete_unused --switches --flags --samples --no-input
84 changes: 84 additions & 0 deletions waffle/management/commands/waffle_delete_unused.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import pathlib

from django.core.management.base import BaseCommand
from waffle import (
get_waffle_flag_model,
get_waffle_switch_model,
get_waffle_sample_model,
)


class Command(BaseCommand):
help = "Delete flags, samples, and switches not present in the code from the Database"

def add_arguments(self, parser):
parser.add_argument(
"--dry-run",
action="store_true",
help="Do not delete anything, just show what would be deleted",
)
parser.add_argument(
"--no-input",
action="store_true",
help="Do not prompt for confirmation",
)
parser.add_argument(
"--switches",
action="store_true",
help="Remove unused switches",
)
parser.add_argument(
"--flags",
action="store_true",
help="Remove unused flags",
)
parser.add_argument(
"--samples",
action="store_true",
help="Remove unused samples",
)

def handle(self, *args, **kwargs):
no_input = kwargs["no_input"]
delete_switches = kwargs["switches"]
delete_flags = kwargs["flags"]
delete_samples = kwargs["samples"]
if delete_switches:
self.delete_model(get_waffle_switch_model(), no_input)
if delete_flags:
self.delete_model(get_waffle_flag_model(), no_input)
if delete_samples:
self.delete_model(get_waffle_sample_model(), no_input)

def delete_model(self, model, no_input):
items = model.objects.all()
for item in items:
if not expression_exists(item.name):
self.stdout.write("%s %s not found in the code" % (model.__name__, item.name))
if no_input or self.confirm("Delete %s ?" % model.__name__):
self.stdout.write("Deleting switch")
item.delete()
else:
self.stdout.write("%s %s found in the code" % (model.__name__, item.name))

def confirm(self, question):
answer = input(question + " [y/N] ").strip()
return answer.lower() == "y"


def expression_in_file(expression, filename):
with open(filename) as file:
content = file.read()
return expression in content


def expression_exists(expression):
for root, dirs, files in os.walk(os.getcwd()):
for file in files:
if not (file.endswith(".py") or file.endswith(".html")): #TODO: make this a list of extensions
continue
filename = pathlib.Path(root) / file
if expression_in_file(expression, filename):
return True
return False
43 changes: 42 additions & 1 deletion waffle/tests/test_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def test_delete_flag(self):
call_command('waffle_delete', flag_names=[name])
self.assertEqual(get_waffle_flag_model().objects.count(), 0)

def test_delete_swtich(self):
def test_delete_switch(self):
""" The command should delete a switch. """
name = 'test_switch'
get_waffle_switch_model().objects.create(name=name)
Expand Down Expand Up @@ -329,3 +329,44 @@ def test_delete_some_but_not_all_records(self):

call_command('waffle_delete', flag_names=[flag_1])
self.assertTrue(get_waffle_flag_model().objects.filter(name=flag_2).exists())


class WaffleDeleteUnused(TestCase):
def test_delete_switches(self):
# we concat the strings so that the switch is not found in the code
get_waffle_switch_model().objects.create(name="FIRST" + "NEVER_FOUND" + "SWITCH", active=True)
get_waffle_switch_model().objects.create(name="SECOND" + "NEVER_FOUND" + "SWITCH", active=True)
# this test is in the search directory, so this very instance will be found
get_waffle_switch_model().objects.create(name="SWITCH_FOUND", active=True)
call_command('waffle_delete_unused', "--no-input", "--switches")
self.assertEqual(1, get_waffle_switch_model().objects.all().count())

def test_delete_samples(self):
# we concat the strings so that the switch is not found in the code
get_waffle_sample_model().objects.create(name="FIRST" + "NEVER_FOUND" + "SAMPLE", percent=0)
get_waffle_sample_model().objects.create(name="SECOND" + "NEVER_FOUND" + "SAMPLE", percent=0)
# this test is in the search directory, so this very instance will be found
get_waffle_sample_model().objects.create(name="SAMPLE_FOUND", percent=0)
call_command('waffle_delete_unused', "--no-input", "--samples")
self.assertEqual(1, get_waffle_sample_model().objects.all().count())

def test_delete_flags(self):
# we concat the strings so that the switch is not found in the code
get_waffle_flag_model().objects.create(name="FIRST" + "NEVER_FOUND" + "FLAG")
get_waffle_flag_model().objects.create(name="SECOND" + "NEVER_FOUND" + "FLAG")
# this test is in the search directory, so this very instance will be found
get_waffle_flag_model().objects.create(name="FLAG_FOUND")
call_command('waffle_delete_unused', "--no-input", "--flags")
self.assertEqual(1, get_waffle_flag_model().objects.all().count())

def test_deletion_confirmation(self):
from unittest import mock
mock.patch('builtins.input', side_effect=["N\n", "Y\n", "N\n"]).start()

get_waffle_switch_model().objects.create(name="FIRST" + "NEVER_FOUND" + "SWITCH", active=True)
get_waffle_sample_model().objects.create(name="FIRST" + "NEVER_FOUND" + "SAMPLE", percent=0)
get_waffle_flag_model().objects.create(name="FIRST" + "NEVER_FOUND" + "FLAG")
call_command('waffle_delete_unused', "--flags", "--samples", "--switches")
self.assertEqual(0, get_waffle_flag_model().objects.all().count())
self.assertEqual(1, get_waffle_sample_model().objects.all().count())
self.assertEqual(1, get_waffle_switch_model().objects.all().count())

0 comments on commit ea82d0c

Please # to comment.