Skip to content

Commit

Permalink
Merge 3eec81b into 78bda24
Browse files Browse the repository at this point in the history
  • Loading branch information
jchate6 committed Apr 19, 2024
2 parents 78bda24 + 3eec81b commit c434f8e
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 3 deletions.
24 changes: 24 additions & 0 deletions docs/targets/target_fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,30 @@ the target detail page.
Any fields added in this way are fully accessible in the TOM Toolkit as ``Target``, and can be used in the same way
as the built-in fields from any custom code you write, the API, or from the admin interface.


Transferring existing ``Extra Field`` Data to you ``Target`` Fields
===================================================================

If you have been using ``Extra Fields`` and have now created a custom target model, you may want to transfer the data
from the ``Extra Fields`` to the new fields in your custom target model. This can be done by running a management
command called ``converttargetextras``. To use this command, be sure to have already created your custom target model.
You can run the command as is for an interactive walkthrough.

.. code:: python
./manage.py converttargetextras
Alternatively, you can run the command with the ``--target_extra`` and/or ``--model_field`` flags to specify one or
more the of the ``Extra Field`` and ``Target Field`` names respectively.

.. code:: python
./manage.py converttargetextras --target_extra extra_bool extra_number --model_field example_bool example_number
This command will go through each target and transfer the data from the ``Extra Field`` to the ``Target Field``. If the
``Target Field`` is already populated, the data will not be transferred. When finished, the ``Extra Field`` data will be
deleted, and you will likely want to remove the ``EXTRA_FIELDS`` setting from your ``settings.py`` file.

Adding ``Extra Fields``
=======================
If a user does not want to create a custom target model, they can use the ``EXTRA_FIELDS``
Expand Down
2 changes: 1 addition & 1 deletion tom_base/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@
# {'name': 'redshift', 'type': 'number', 'default': 0},
# {'name': 'discoverer', 'type': 'string'},
# {'name': 'eligible', 'type': 'boolean', 'hidden': True},
# {'name': 'dicovery_date', 'type': 'datetime'}
# {'name': 'discovery_date', 'type': 'datetime'}
# ]
EXTRA_FIELDS = []

Expand Down
4 changes: 2 additions & 2 deletions tom_setup/templates/tom_setup/settings.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,9 @@ HARVESTERS = {
# For example:
# EXTRA_FIELDS = [
# {'name': 'redshift', 'type': 'number'},
# {'name': 'discoverer', 'type': 'string'}
# {'name': 'discoverer', 'type': 'string'},
# {'name': 'eligible', 'type': 'boolean'},
# {'name': 'dicovery_date', 'type': 'datetime'}
# {'name': 'discovery_date', 'type': 'datetime'}
# ]
EXTRA_FIELDS = []

Expand Down
162 changes: 162 additions & 0 deletions tom_targets/management/commands/converttargetextras.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from django.core.management.base import BaseCommand
from django.conf import settings

from tom_targets.base_models import BaseTarget
from tom_targets.models import Target, TargetExtra


class Command(BaseCommand):
"""
This command converts a given TargetExtra into a model field in the current Target model.
This requires a model field to already exist in your UserDefinedTarget model for each Extra Field you wish to
convert. If you have not created a UserDefinedTarget model, you should follow the example given in the
documentation: https://tom-toolkit.readthedocs.io/en/stable/targets/target_fields.html#extending-the-target-model
Example:
./manage.py converttargetextras --target_extra redshift discovery_date --model_field redshift discovery_date
"""

help = 'A Helper command to convert target extras into UserDefinedTarget Fields'

def add_arguments(self, parser):
parser.add_argument(
'--target_extra',
nargs='+',
help='TargetExtra to convert into a model field. Accepts multiple TargetExtras. '
'(Leave blank for interactive.)'
)
parser.add_argument(
'--model_field',
nargs='+',
default=[], # Default to empty list to allow for interactive mode
help='Model Fields for UserDefinedTarget to accept TargetExtra. Accepts multiple Model Fields. '
'Order must match --target_extra order for multiple entries. '
'(Leave blank for interactive.)'
)
parser.add_argument(
'--confirm',
action='store_true',
help='Confirm each Target Extra -> Model Field conversion first.',
)

def prompt_extra_field(self, extra_field_keys):
"""
Interactive Mode -- Prompt the user to choose a TargetExtra to convert
extra_field_keys: List of valid TargetExtra keys from settings.py
"""
prompt = f'Which Extra Field would you like to convert?\n{self.style.WARNING(extra_field_keys)}\n'
while True:
chosen_extra = input(prompt)
if chosen_extra in extra_field_keys:
break
else:
self.stdout.write(self.style.ERROR("I don't recognize that field. "
"Please choose from the list."))
return chosen_extra

def prompt_model_field(self, model_field_keys, chosen_extra):
"""
Interactive Mode -- Prompt the user to choose a Model Field to convert the TargetExtra into
model_field_keys: list of valid fields available for the Target Model
chosen_extra: key for the selected TargetExtra
"""
prompt = f'What is the name of the model field you would like to convert {self.style.SUCCESS(chosen_extra)}' \
f' into? (Leave blank to skip)\n{self.style.WARNING(model_field_keys)}\n'
while True:
chosen_model_field = input(prompt)
if chosen_model_field in model_field_keys:
break
elif not chosen_model_field:
self.stdout.write(f'Skipping TargetExtra: {self.style.SUCCESS(chosen_extra)}.')
return None
else:
self.stdout.write(self.style.ERROR("I don't recognize that field. "
"Please choose from the list."))
return chosen_model_field

def confirm_conversion(self, chosen_extra, chosen_model_field):
"""
Interactive Mode -- Ask for confirmation before converting a Target Extra
"""
prompt = (f'Are you sure that you want to convert the TargetExtra:{self.style.SUCCESS(chosen_extra)} to '
f'the {Target.__name__} model field:{self.style.SUCCESS(chosen_model_field)} for all Targets?\n'
f' {self.style.WARNING("(y/N)")}\n')
while True:
response = input(prompt).lower()
if not response or response == 'n' or response == 'no':
self.stdout.write(f'Skipping TargetExtra: {self.style.SUCCESS(chosen_extra)}.')
return False
elif response == 'y' or response == 'yes':
return True
else:
self.stdout.write('Invalid response. Please try again.')

def convert_target_extra(self, chosen_extra, chosen_model_field):
"""
Perform the actual conversion from a `chosen_extra` to a `chosen_model_field` for each target that has one of
these TargetExtras.
chosen_extra: key for the selected TargetExtra.
chosen_model_field: name of the selected Target field.
"""
for extra in TargetExtra.objects.filter(key=chosen_extra):
target = Target.objects.get(pk=extra.target.pk)
if getattr(target, chosen_model_field, None):
self.stdout.write(f"{self.style.ERROR('Warning:')} {target}.{chosen_model_field} "
f"already has a value: {getattr(target, chosen_model_field)}. Skipping.")
continue
self.stdout.write(f"Setting {Target.__name__}.{chosen_model_field} to {extra.value} for "
f"{target}.")
setattr(target, chosen_model_field, extra.value)
target.save()
extra.delete()
else:
self.stdout.write(f"{self.style.ERROR('Warning:')} No TargetExtra found for "
f"{self.style.SUCCESS(chosen_extra)}.")

def handle(self, *args, **options):
chosen_extras = options['target_extra']
chosen_model_fields = options['model_field']

# Get all the extra field keys
extra_field_keys = [field['name'] for field in settings.EXTRA_FIELDS]

# Get all the new model fields
target_model = Target
model_field_keys = [field.name for field in target_model._meta.get_fields()
if field not in BaseTarget._meta.get_fields() and field.name != 'basetarget_ptr']

# If no Target Extras were provided, prompt user
if not chosen_extras:
chosen_extras = [self.prompt_extra_field(extra_field_keys)]

for i, chosen_extra in enumerate(chosen_extras):
# Check that inputs are valid.
if chosen_extra not in extra_field_keys:
self.stdout.write(self.style.ERROR(f"Skipping {chosen_extra} since it is not a valid TargetExtra."))
continue
try:
chosen_model_field = chosen_model_fields[i]
except IndexError:
# If no Model Field was provided, prompt user
chosen_model_field = self.prompt_model_field(model_field_keys, chosen_extra)
if not chosen_model_field:
continue
if chosen_extra not in extra_field_keys:
self.stdout.write(f'{self.style.ERROR("Warning:")} Skipping {chosen_extra} since it is not a valid'
f' TargetExtra.')
continue
if chosen_model_field not in model_field_keys:
self.stdout.write(f'{self.style.ERROR("Warning:")} Skipping {chosen_model_field} since it is not a '
f'valid target field for {Target.__name__}.')
continue

if options['confirm']:
confirmed = self.confirm_conversion(chosen_extra, chosen_model_field)
if not confirmed:
continue

self.convert_target_extra(chosen_extra, chosen_model_field)

return

0 comments on commit c434f8e

Please sign in to comment.