Przeglądaj źródła

HUE-9614 [Deprecation Warning] on_delete will be a required arg for ForeignKey in Django 2.0

ayush.goyal 5 lat temu
rodzic
commit
fa87482247

+ 17 - 14
apps/beeswax/src/beeswax/models.py

@@ -64,14 +64,15 @@ class QueryHistory(models.Model):
                  (librdbms_dbms.MYSQL, 'MySQL'), (librdbms_dbms.POSTGRESQL, 'PostgreSQL'),
                  (librdbms_dbms.SQLITE, 'sqlite'), (librdbms_dbms.ORACLE, 'oracle'))
 
-  owner = models.ForeignKey(User, db_index=True)
+  owner = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
   query = models.TextField()
 
   last_state = models.IntegerField(db_index=True)
   has_results = models.BooleanField(default=False)          # If true, this query will eventually return tabular results.
   submission_date = models.DateTimeField(auto_now_add=True)
   # In case of multi statements in a query, these are the id of the currently running statement
-  server_id = models.CharField(max_length=1024, null=True)  # Aka secret, only query in the "submitted" state is allowed to have no server_id
+  # Aka secret, only query in the "submitted" state is allowed to have no server_id
+  server_id = models.CharField(max_length=1024, null=True)
   server_guid = models.CharField(max_length=1024, null=True, default=None)
   statement_number = models.SmallIntegerField(default=0)    # The index of the currently running statement
   operation_type = models.SmallIntegerField(null=True)
@@ -84,7 +85,8 @@ class QueryHistory(models.Model):
   server_type = models.CharField(max_length=128, help_text=_('Type of the query server.'), default=BEESWAX, choices=SERVER_TYPE)
   query_type = models.SmallIntegerField(help_text=_('Type of the query.'), default=HQL, choices=((HQL, 'HQL'), (IMPALA, 'IMPALA')))
 
-  design = models.ForeignKey('SavedQuery', to_field='id', null=True) # Some queries (like read/create table) don't have a design
+  # Some queries (like read/create table) don't have a design
+  design = models.ForeignKey('SavedQuery', on_delete=models.CASCADE, to_field='id', null=True)
   notify = models.BooleanField(default=False)                        # Notify on completion
 
   is_redacted = models.BooleanField(default=False)
@@ -220,14 +222,14 @@ def make_query_context(type, info):
 class HiveServerQueryHistory(QueryHistory):
   # Map from (thrift) server state
   STATE_MAP = {
-    TOperationState.INITIALIZED_STATE : QueryHistory.STATE.submitted,
-    TOperationState.RUNNING_STATE     : QueryHistory.STATE.running,
-    TOperationState.FINISHED_STATE    : QueryHistory.STATE.available,
-    TOperationState.CANCELED_STATE    : QueryHistory.STATE.failed,
-    TOperationState.CLOSED_STATE      : QueryHistory.STATE.expired,
-    TOperationState.ERROR_STATE       : QueryHistory.STATE.failed,
-    TOperationState.UKNOWN_STATE      : QueryHistory.STATE.failed,
-    TOperationState.PENDING_STATE     : QueryHistory.STATE.submitted,
+    TOperationState.INITIALIZED_STATE: QueryHistory.STATE.submitted,
+    TOperationState.RUNNING_STATE: QueryHistory.STATE.running,
+    TOperationState.FINISHED_STATE: QueryHistory.STATE.available,
+    TOperationState.CANCELED_STATE: QueryHistory.STATE.failed,
+    TOperationState.CLOSED_STATE: QueryHistory.STATE.expired,
+    TOperationState.ERROR_STATE: QueryHistory.STATE.failed,
+    TOperationState.UKNOWN_STATE: QueryHistory.STATE.failed,
+    TOperationState.PENDING_STATE: QueryHistory.STATE.submitted,
   }
 
   node_type = HIVE_SERVER2
@@ -266,7 +268,7 @@ class SavedQuery(models.Model):
   TYPES_MAPPING = {'beeswax': HQL, 'hql': HQL, 'impala': IMPALA, 'rdbms': RDBMS, 'spark': SPARK}
 
   type = models.IntegerField(null=False)
-  owner = models.ForeignKey(User, db_index=True)
+  owner = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
   # Data is a json of dictionary. See the beeswax.design module.
   data = models.TextField(max_length=65536)
   name = models.CharField(max_length=80)
@@ -457,7 +459,7 @@ class Session(models.Model):
   """
   A sessions is bound to a user and an application (e.g. Bob with the Impala application).
   """
-  owner = models.ForeignKey(User, db_index=True)
+  owner = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
   status_code = models.PositiveSmallIntegerField()  # ttypes.TStatusCode
   secret = models.TextField(max_length='100')
   guid = models.TextField(max_length='100')
@@ -485,7 +487,8 @@ class Session(models.Model):
 
 
 class QueryHandle(object):
-  def __init__(self, secret=None, guid=None, operation_type=None, has_result_set=None, modified_row_count=None, log_context=None, session_guid=None, session_id=None):
+  def __init__(self, secret=None, guid=None, operation_type=None,
+   has_result_set=None, modified_row_count=None, log_context=None, session_guid=None, session_id=None):
     self.secret = secret
     self.guid = guid
     self.operation_type = operation_type

+ 6 - 6
apps/jobsub/src/jobsub/models.py

@@ -37,7 +37,7 @@ class JobDesign(models.Model):
 
   Contains CMS information for "job designs".
   """
-  owner = models.ForeignKey(User)
+  owner = models.ForeignKey(User, on_delete=models.CASCADE)
   name = models.CharField(max_length=40)
   description = models.CharField(max_length=1024)
   last_modified = models.DateTimeField(auto_now=True)
@@ -98,7 +98,7 @@ class OozieAction(models.Model):
   reference. See
   https://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance
   """
-  PARAM_FIELDS = ( )    # Nothing is parameterized by default
+  PARAM_FIELDS = ()    # Nothing is parameterized by default
 
   # This allows the code to easily figure out which subclass to access
   action_type = models.CharField(max_length=64, blank=False)
@@ -129,7 +129,7 @@ class OozieDesign(models.Model):
   stored in the Oozie*Action models.
   """
   # Generic stuff
-  owner = models.ForeignKey(User)
+  owner = models.ForeignKey(User, on_delete=models.CASCADE)
   name = models.CharField(max_length=64, blank=False,
       help_text=_('Name of the design, which must be unique per user.'))
   description = models.CharField(max_length=1024, blank=True)
@@ -138,7 +138,7 @@ class OozieDesign(models.Model):
   # Action. Avoid using `root_action' directly, because it only gives you the
   # intermediate table (i.e. OozieAction). You want to use `get_root_action()'
   # most of the time.
-  root_action = models.ForeignKey(OozieAction)
+  root_action = models.ForeignKey(OozieAction, on_delete=models.CASCADE)
 
   def get_root_action(self):
     """Return the concrete action object, not just a generic OozieAction"""
@@ -260,7 +260,7 @@ class JobHistory(models.Model):
 
   Contains informatin on submitted jobs/workflows.
   """
-  owner = models.ForeignKey(User)
+  owner = models.ForeignKey(User, on_delete=models.CASCADE)
   submission_date = models.DateTimeField(auto_now=True)
   job_id = models.CharField(max_length=128)
-  design = models.ForeignKey(OozieDesign)
+  design = models.ForeignKey(OozieDesign, on_delete=models.CASCADE)

+ 179 - 83
apps/oozie/src/oozie/models.py

@@ -28,7 +28,7 @@ import sys
 import time
 import zipfile
 
-from datetime import datetime,  timedelta
+from datetime import datetime, timedelta
 from string import Template
 from itertools import chain
 
@@ -120,7 +120,13 @@ class Job(models.Model):
   """
   Base class for Oozie Workflows, Coordinators and Bundles.
   """
-  owner = models.ForeignKey(User, db_index=True, verbose_name=_t('Owner'), help_text=_t('Person who can modify the job.')) # Deprecated
+  owner = models.ForeignKey(
+    User,
+    on_delete=models.CASCADE,
+    db_index=True,
+    verbose_name=_t('Owner'),
+    help_text=_t('Person who can modify the job.')
+  ) # Deprecated
   name = models.CharField(max_length=255, blank=False, validators=[name_validator], # Deprecated
       help_text=_t('Name of the job, which must be unique per user.'), verbose_name=_t('Name'))
   description = models.CharField(max_length=1024, blank=True, verbose_name=_t('Description'), # Deprecated
@@ -340,8 +346,8 @@ class WorkflowManager(models.Manager):
 
 class Workflow(Job):
   is_single = models.BooleanField(default=False)
-  start = models.ForeignKey('Start', related_name='start_workflow', blank=True, null=True)
-  end  = models.ForeignKey('End', related_name='end_workflow',  blank=True, null=True)
+  start = models.ForeignKey('Start', on_delete=models.CASCADE, related_name='start_workflow', blank=True, null=True)
+  end = models.ForeignKey('End', on_delete=models.CASCADE, related_name='end_workflow', blank=True, null=True)
   job_xml = models.CharField(max_length=PATH_MAX, default='', blank=True, verbose_name=_t('Job XML'),
                              help_text=_t('Refer to a Hadoop JobConf job.xml file bundled in the workflow deployment directory. '
                                           'Properties specified in the Job Properties element override properties specified in the '
@@ -554,7 +560,9 @@ class Workflow(Job):
     actions_index = dict([(action.name, action) for action in actions])
     controls_index = dict([(control.name.strip(':'), control) for control in controls])
 
-    return django_mako.render_to_string(template, {'nodes': self.get_hierarchy(), 'index': index, 'actions': actions_index, 'controls': controls_index})
+    return django_mako.render_to_string(
+      template, {'nodes': self.get_hierarchy(), 'index': index, 'actions': actions_index, 'controls': controls_index}
+    )
 
   @classmethod
   def gen_status_graph_from_xml(cls, user, oozie_workflow):
@@ -566,7 +574,7 @@ class Workflow(Job):
         workflow.save()
 
         import_workflow(workflow, oozie_workflow.definition)
-        graph =  workflow.gen_status_graph(oozie_workflow)
+        graph = workflow.gen_status_graph(oozie_workflow)
         node_list = workflow.node_list
         workflow.delete(skip_trash=True)
         return graph, node_list
@@ -630,8 +638,8 @@ class Link(models.Model):
   # Links to exclude when using get_children_link(), get_parent_links() in the API
   META_LINKS = ('related',)
 
-  parent = models.ForeignKey('Node', related_name='child_node')
-  child = models.ForeignKey('Node', related_name='parent_node', verbose_name='')
+  parent = models.ForeignKey('Node', on_delete=models.CASCADE, related_name='child_node')
+  child = models.ForeignKey('Node', on_delete=models.CASCADE, related_name='parent_node', verbose_name='')
 
   name = models.CharField(max_length=40)
   comment = models.CharField(max_length=1024, default='', blank=True)
@@ -662,7 +670,7 @@ class Node(models.Model):
                                  help_text=_t('The purpose of the action.'))
   node_type = models.CharField(max_length=64, blank=False, verbose_name=_t('Type'),
                                help_text=_t('The type of action (e.g. MapReduce, Pig...)'))
-  workflow = models.ForeignKey(Workflow)
+  workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE)
   children = models.ManyToManyField('self', related_name='parents', symmetrical=False, through=Link)
   data = models.TextField(blank=True, default=json.dumps({}))
 
@@ -871,8 +879,11 @@ class Mapreduce(Action):
       help_text=_t('List of names or paths of the archives to be added to the distributed cache.'))
   job_properties = models.TextField(default='[]', verbose_name=_t('Hadoop job properties'),
                                     help_text=_t('For the job configuration (e.g. mapred.job.queue.name=production)'))
-  jar_path = models.CharField(max_length=PATH_MAX, verbose_name=_t('Jar name'),
-                              help_text=_t('Name or path to the %(program)s jar file on HDFS. E.g. examples.jar.') % {'program': 'MapReduce'})
+  jar_path = models.CharField(
+    max_length=PATH_MAX,
+    verbose_name=_t('Jar name'),
+    help_text=_t('Name or path to the %(program)s jar file on HDFS. E.g. examples.jar.') % {'program': 'MapReduce'}
+  )
   prepares = models.TextField(default="[]", verbose_name=_t('Prepares'),
                               help_text=_t('List of absolute paths to delete and then to create before starting the application. '
                                            'This should be used exclusively for directory cleanup.'))
@@ -948,11 +959,14 @@ class Java(Action):
                              help_text=_t('Refer to a Hadoop JobConf job.xml file bundled in the workflow deployment directory. '
                                           'Properties specified in the Job Properties element override properties specified in the '
                                           'files specified in the Job XML element.'))
-  capture_output = models.BooleanField(default=False, verbose_name=_t('Capture output'),
-                              help_text=_t('Capture output of the stdout of the %(program)s command execution. The %(program)s '
-                                           'command output must be in Java Properties file format and it must not exceed 2KB. '
-                                           'From within the workflow definition, the output of an %(program)s action node is accessible '
-                                           'via the String action:output(String node, String key) function') % {'program': node_type.title()})
+  capture_output = models.BooleanField(
+    default=False,
+    verbose_name=_t('Capture output'),
+    help_text=_t('Capture output of the stdout of the %(program)s command execution. The %(program)s '
+                  'command output must be in Java Properties file format and it must not exceed 2KB. '
+                  'From within the workflow definition, the output of an %(program)s action node is accessible '
+                  'via the String action:output(String node, String key) function') % {'program': node_type.title()}
+  )
 
   def get_properties(self):
     return json.loads(self.job_properties)
@@ -1009,10 +1023,17 @@ class Hive(Action):
   PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'hive'
 
-  script_path = models.CharField(max_length=256, blank=False, verbose_name=_t('Script name'),
-                                 help_text=_t('Script name or path to the %(type)s script. E.g. my_script.sql.') % {'type': node_type.title()})
-  params = models.TextField(default="[]", verbose_name=_t('Parameters'),
-                            help_text=_t('The %(type)s parameters of the script. E.g. N=5, INPUT=${inputDir}')  % {'type': node_type.title()})
+  script_path = models.CharField(
+    max_length=256,
+    blank=False,
+    verbose_name=_t('Script name'),
+    help_text=_t('Script name or path to the %(type)s script. E.g. my_script.sql.') % {'type': node_type.title()}
+  )
+  params = models.TextField(
+    default="[]",
+    verbose_name=_t('Parameters'),
+    help_text=_t('The %(type)s parameters of the script. E.g. N=5, INPUT=${inputDir}')  % {'type': node_type.title()}
+  )
   files = models.TextField(default="[]", verbose_name=_t('Files'),
       help_text=_t('List of names or paths of files to be added to the distributed cache and the task running directory.'))
   archives = models.TextField(default="[]", verbose_name=_t('Archives'),
@@ -1023,8 +1044,14 @@ class Hive(Action):
   prepares = models.TextField(default="[]", verbose_name=_t('Prepares'),
                               help_text=_t('List of absolute paths to delete, then create, before starting the application. '
                                            'This should be used exclusively for directory cleanup.'))
-  job_xml = models.CharField(max_length=PATH_MAX, default='hive-config.xml', blank=True, verbose_name=_t('Job XML'),
-                             help_text=_t('Refer to a Hive hive-config.xml file bundled in the workflow deployment directory. Pick a name different than hive-site.xml.'))
+  job_xml = models.CharField(
+    max_length=PATH_MAX,
+    default='hive-config.xml',
+    blank=True,
+    verbose_name=_t('Job XML'),
+    help_text=_t('Refer to a Hive hive-config.xml file bundled in the workflow deployment directory. '
+                 'Pick a name different than hive-site.xml.')
+  )
 
   def get_properties(self):
     return json.loads(self.job_properties)
@@ -1046,9 +1073,13 @@ class Sqoop(Action):
   PARAM_FIELDS = ('files', 'archives', 'job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'sqoop'
 
-  script_path = models.TextField(blank=True, verbose_name=_t('Command'), default='',
-                                 help_text=_t('The full %(type)s command. Either put it here or split it by spaces and insert the parts as multiple parameters below.')
-                                             % {'type': node_type.title()})
+  script_path = models.TextField(
+    blank=True,
+    verbose_name=_t('Command'),
+    default='',
+    help_text=_t('The full %(type)s command. Either put it here or split it by spaces and insert the parts as multiple parameters below.')
+                % {'type': node_type.title()}
+  )
   params = models.TextField(default="[]", verbose_name=_t('Parameters'),
                             help_text=_t('If no command is specified, split the command by spaces and insert the %(type)s parameters '
                                          'here e.g. import, --connect, jdbc:hsqldb:file:db.hsqldb, ...') % {'type': node_type.title()})
@@ -1095,11 +1126,14 @@ class Ssh(Action):
                              help_text=_t('The command that will be executed.'))
   params = models.TextField(default="[]", verbose_name=_t('Arguments'),
                             help_text=_t('The arguments of the %(type)s command.')  % {'type': node_type.title()})
-  capture_output = models.BooleanField(default=False, verbose_name=_t('Capture output'),
-                              help_text=_t('Capture output of the stdout of the %(program)s command execution. The %(program)s '
-                                           'command output must be in Java properties file format and it must not exceed 2KB. '
-                                           'From within the workflow definition, the output of an %(program)s action node is accessible '
-                                           'via the String action:output(String node, String key) function') % {'program': node_type.title()})
+  capture_output = models.BooleanField(
+    default=False,
+    verbose_name=_t('Capture output'),
+    help_text=_t('Capture output of the stdout of the %(program)s command execution. The %(program)s '
+                  'command output must be in Java properties file format and it must not exceed 2KB. '
+                  'From within the workflow definition, the output of an %(program)s action node is accessible '
+                  'via the String action:output(String node, String key) function') % {'program': node_type.title()}
+  )
 
   def get_params(self):
     return json.loads(self.params)
@@ -1126,11 +1160,14 @@ class Shell(Action):
                              help_text=_t('Refer to a Hadoop JobConf job.xml file bundled in the workflow deployment directory. '
                                           'Properties specified in the Job Properties element override properties specified in the '
                                           'files specified in the Job XML element.'))
-  capture_output = models.BooleanField(default=False, verbose_name=_t('Capture output'),
-                              help_text=_t('Capture output of the stdout of the %(program)s command execution. The %(program)s '
-                                           'command output must be in Java Properties file format and it must not exceed 2KB. '
-                                           'From within the workflow definition, the output of an %(program)s action node is accessible '
-                                           'via the String action:output(String node, String key) function') % {'program': node_type.title()})
+  capture_output = models.BooleanField(
+    default=False,
+    verbose_name=_t('Capture output'),
+    help_text=_t('Capture output of the stdout of the %(program)s command execution. The %(program)s '
+                  'command output must be in Java Properties file format and it must not exceed 2KB. '
+                  'From within the workflow definition, the output of an %(program)s action node is accessible '
+                  'via the String action:output(String node, String key) function') % {'program': node_type.title()}
+  )
 
   def get_properties(self):
     return json.loads(self.job_properties)
@@ -1152,9 +1189,12 @@ class DistCp(Action):
   PARAM_FIELDS = ('job_properties', 'params', 'prepares', 'sla', 'credentials')
   node_type = 'distcp'
 
-  params = models.TextField(default="[]", verbose_name=_t('Arguments'),
-                            help_text=_t('The arguments of the %(type)s command. Put options first, then source paths, then destination path.')
-                                        % {'type': node_type.title()})
+  params = models.TextField(
+    default="[]",
+    verbose_name=_t('Arguments'),
+    help_text=_t('The arguments of the %(type)s command. Put options first, then source paths, then destination path.')
+                % {'type': node_type.title()}
+  )
   job_properties = models.TextField(default='[]', verbose_name=_t('Hadoop job properties'),
                                     help_text=_t('For the job configuration (e.g. mapred.job.queue.name=production'))
   prepares = models.TextField(default="[]", verbose_name=_t('Prepares'),
@@ -1188,9 +1228,13 @@ class Fs(Action):
                                          'If the directory already exist it does a no-op.'))
   moves = models.TextField(default="[]", verbose_name=_t('Move file'), blank=True,
                             help_text=_t('Move a file or directory to another path.'))
-  chmods = models.TextField(default="[]", verbose_name=_t('Change permissions'), blank=True,
-                            help_text=_t('Change the permissions for the specified path. Permissions can be specified using the Unix Symbolic '
-                                         'representation (e.g. -rwxrw-rw-) or an octal representation (755).'))
+  chmods = models.TextField(
+    default="[]",
+    verbose_name=_t('Change permissions'),
+    blank=True,
+    help_text=_t('Change the permissions for the specified path. Permissions can be specified using the Unix Symbolic '
+                  'representation (e.g. -rwxrw-rw-) or an octal representation (755).')
+  )
   touchzs = models.TextField(default="[]", verbose_name=_t('Create or touch a file'), blank=True,
                             help_text=_t('Creates a zero length file in the specified path if none exists or touch it.'))
 
@@ -1225,12 +1269,22 @@ class SubWorkflow(Action):
   PARAM_FIELDS = ('subworkflow', 'propagate_configuration', 'job_properties', 'sla', 'credentials')
   node_type = 'subworkflow'
 
-  sub_workflow = models.ForeignKey(Workflow, default=None, db_index=True, blank=True, null=True, verbose_name=_t('Sub-workflow'),
-                            help_text=_t('The sub-workflow application to include. You must own all the sub-workflows.'))
+  sub_workflow = models.ForeignKey(
+    Workflow,
+    on_delete=models.CASCADE,
+    default=None,
+    db_index=True,
+    blank=True, null=True,
+    verbose_name=_t('Sub-workflow'),
+    help_text=_t('The sub-workflow application to include. You must own all the sub-workflows.')
+  )
   propagate_configuration = models.BooleanField(default=True, verbose_name=_t('Propagate configuration'), blank=True,
                             help_text=_t('If the workflow job configuration should be propagated to the child workflow.'))
-  job_properties = models.TextField(default='[]', verbose_name=_t('Hadoop job properties'),
-                                    help_text=_t('Can be used to specify the job properties that are required to run the child workflow job.'))
+  job_properties = models.TextField(
+    default='[]',
+    verbose_name=_t('Hadoop job properties'),
+    help_text=_t('Can be used to specify the job properties that are required to run the child workflow job.')
+  )
 
   def get_properties(self):
     return json.loads(self.job_properties)
@@ -1240,17 +1294,21 @@ class Generic(Action):
   PARAM_FIELDS = ('xml', 'credentials', 'sla', 'credentials')
   node_type = 'generic'
 
-  xml = models.TextField(default='', verbose_name=_t('XML of the custom action'),
-                         help_text=_t('This will be inserted verbatim in the action %(action)s. '
-                                      'E.g. all the XML content like %(xml_action)s '
-                                      'will be inserted into the action and produce %(full_action)s') % {
-                                      'action': '<action name="email">...</action>',
-                                      'xml_action': '<email><cc>hue@hue.org</cc></email>',
-                                      'full_action': '<action name="email"><email><cc>hue@hue.org</cc></email><ok/><error/></action>'})
+  xml = models.TextField(
+    default='',
+    verbose_name=_t('XML of the custom action'),
+    help_text=_t('This will be inserted verbatim in the action %(action)s. '
+                'E.g. all the XML content like %(xml_action)s '
+                'will be inserted into the action and produce %(full_action)s') % {
+                'action': '<action name="email">...</action>',
+                'xml_action': '<email><cc>hue@hue.org</cc></email>',
+                'full_action': '<action name="email"><email><cc>hue@hue.org<'
+                '/cc></email><ok/><error/></action>'}
+  )
 
 
-Action.types = (Mapreduce.node_type, Streaming.node_type, Java.node_type, Pig.node_type, Hive.node_type, Sqoop.node_type, Ssh.node_type, Shell.node_type,
-                DistCp.node_type, Fs.node_type, Email.node_type, SubWorkflow.node_type, Generic.node_type)
+Action.types = (Mapreduce.node_type, Streaming.node_type, Java.node_type, Pig.node_type, Hive.node_type, Sqoop.node_type, Ssh.node_type,
+  Shell.node_type, DistCp.node_type, Fs.node_type, Email.node_type, SubWorkflow.node_type, Generic.node_type)
 
 
 class ControlFlow(Node):
@@ -1411,13 +1469,19 @@ class Coordinator(Job):
                                                            'data is periodically created.')) # unused
   frequency_unit = models.CharField(max_length=20, choices=FREQUENCY_UNITS, default='days', verbose_name=_t('Frequency unit'),
                                     help_text=_t('The unit of the rate at which data is periodically created.')) # unused
-  timezone = models.CharField(max_length=24, choices=TIMEZONES, default='America/Los_Angeles', verbose_name=_t('Timezone'),
-                              help_text=_t('The timezone of the coordinator. Only used for managing the daylight saving time changes when combining several coordinators.'))
+  timezone = models.CharField(
+    max_length=24,
+    choices=TIMEZONES,
+    default='America/Los_Angeles',
+    verbose_name=_t('Timezone'),
+    help_text=_t('The timezone of the coordinator. '
+      'Only used for managing the daylight saving time changes when combining several coordinators.')
+  )
   start = models.DateTimeField(auto_now=True, verbose_name=_t('Start'),
                                help_text=_t('When to start the first workflow.'))
   end = models.DateTimeField(auto_now=True, verbose_name=_t('End'),
                              help_text=_t('When to start the last workflow.'))
-  coordinatorworkflow = models.ForeignKey(Workflow, null=True, verbose_name=_t('Workflow'),
+  coordinatorworkflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, null=True, verbose_name=_t('Workflow'),
                                help_text=_t('The workflow to schedule repeatedly.'))
   timeout = models.SmallIntegerField(null=True, blank=True, verbose_name=_t('Timeout'),
                                      help_text=_t('Number of minutes the coordinator action will be in '
@@ -1433,11 +1497,19 @@ class Coordinator(Job):
                                               'actions in the coordinator engine. The different execution strategies are \'oldest first\', '
                                               '\'newest first\' and \'last one only\'. A backlog normally happens because of delayed '
                                               'input data, concurrency control or because manual re-runs of coordinator jobs.'))
-  throttle = models.PositiveSmallIntegerField(null=True, blank=True, choices=FREQUENCY_NUMBERS, verbose_name=_t('Throttle'),
-                                 help_text=_t('The materialization or creation throttle value for its coordinator actions. '
-                                              'Number of maximum coordinator actions that are allowed to be in WAITING state concurrently.'))
-  job_properties = models.TextField(default='[]', verbose_name=_t('Workflow properties'),
-                                    help_text=_t('Additional properties to transmit to the workflow, e.g. limit=100, and EL functions, e.g. username=${coord:user()}'))
+  throttle = models.PositiveSmallIntegerField(
+    null=True,
+    blank=True,
+    choices=FREQUENCY_NUMBERS,
+    verbose_name=_t('Throttle'),
+    help_text=_t('The materialization or creation throttle value for its coordinator actions. '
+                'Number of maximum coordinator actions that are allowed to be in WAITING state concurrently.')
+  )
+  job_properties = models.TextField(
+    default='[]',
+    verbose_name=_t('Workflow properties'),
+    help_text=_t('Additional properties to transmit to the workflow, e.g. limit=100, and EL functions, e.g. username=${coord:user()}')
+  )
 
   HUE_ID = 'hue-id-c'
   ICON = 'oozie/art/icon_oozie_coordinator_48.png'
@@ -1459,7 +1531,8 @@ class Coordinator(Job):
     if mapping is None:
       mapping = {}
     tmpl = "editor/gen/coordinator.xml.mako"
-    return re.sub(re.compile('\s*\n+', re.MULTILINE), '\n', django_mako.render_to_string(tmpl, {'coord': self, 'mapping': mapping})).encode('utf-8', 'xmlcharrefreplace')
+    return re.sub(re.compile('\s*\n+', re.MULTILINE), '\n', 
+      django_mako.render_to_string(tmpl, {'coord': self, 'mapping': mapping})).encode('utf-8', 'xmlcharrefreplace')
 
   def clone(self, new_owner=None):
     datasets = Dataset.objects.filter(coordinator=self)
@@ -1684,24 +1757,43 @@ class Dataset(models.Model):
                                                            'data is periodically created.'))
   frequency_unit = models.CharField(max_length=20, choices=FREQUENCY_UNITS, default='days', verbose_name=_t('Frequency unit'),
                                     help_text=_t('The unit of the rate at which data is periodically created.'))
-  uri = models.CharField(max_length=1024, default='/data/${YEAR}${MONTH}${DAY}', verbose_name=_t('URI'),
-                         help_text=_t('The URI template that identifies the dataset and can be resolved into concrete URIs to identify a particular '
-                                      'dataset instance. The URI consist of constants (e.g. ${YEAR}/${MONTH}) and '
-                                      'configuration properties (e.g. /home/${USER}/projects/${PROJECT})'))
-  timezone = models.CharField(max_length=24, choices=TIMEZONES, default='America/Los_Angeles', verbose_name=_t('Timezone'),
-                              help_text=_t('The timezone of the dataset. Only used for managing the daylight saving time changes when combining several datasets.'))
+  uri = models.CharField(
+    max_length=1024,
+    default='/data/${YEAR}${MONTH}${DAY}',
+    verbose_name=_t('URI'),
+    help_text=_t('The URI template that identifies the dataset and can be resolved into concrete URIs to identify a particular '
+                'dataset instance. The URI consist of constants (e.g. ${YEAR}/${MONTH}) and '
+                'configuration properties (e.g. /home/${USER}/projects/${PROJECT})')
+  )
+  timezone = models.CharField(
+    max_length=24,
+    choices=TIMEZONES,
+    default='America/Los_Angeles',
+    verbose_name=_t('Timezone'),
+    help_text=_t('The timezone of the dataset. Only used for managing the daylight saving time changes when combining several datasets.')
+  )
   done_flag = models.CharField(max_length=64, blank=True, default='', verbose_name=_t('Done flag'),
                                help_text=_t('The done file for the data set. If the Done flag is not specified, then Oozie '
                                             'configures Hadoop to create a _SUCCESS file in the output directory. If Done '
                                             'flag is set to empty, then Coordinator looks for the existence of the directory itself.'))
-  coordinator = models.ForeignKey(Coordinator, verbose_name=_t('Coordinator'),
+  coordinator = models.ForeignKey(Coordinator, on_delete=models.CASCADE, verbose_name=_t('Coordinator'),
                                   help_text=_t('The coordinator associated with this data.'))
   instance_choice = models.CharField(max_length=10, default='default', verbose_name=_t('Instance type'),
                                help_text=_t('Customize the date instance(s), e.g. define a range of dates, use EL functions...'))
-  advanced_start_instance  = models.CharField(max_length=128, default='0', verbose_name=_t('Start instance'),
-                               help_text=_t('Shift the frequency for gettting past/future start date or enter verbatim the Oozie start instance, e.g. ${coord:current(0)}'))
-  advanced_end_instance  = models.CharField(max_length=128, blank=True, default='0', verbose_name=_t('End instance'),
-                               help_text=_t('Optional: Shift the frequency for gettting past/future end dates or enter verbatim the Oozie end instance.'))
+  advanced_start_instance = models.CharField(
+    max_length=128,
+    default='0',
+    verbose_name=_t('Start instance'),
+    help_text=_t('Shift the frequency for gettting past/future start date or enter verbatim the Oozie start instance, '
+                 'e.g. ${coord:current(0)}')
+  )
+  advanced_end_instance = models.CharField(
+    max_length=128,
+    blank=True,
+    default='0',
+    verbose_name=_t('End instance'),
+    help_text=_t('Optional: Shift the frequency for gettting past/future end dates or enter verbatim the Oozie end instance.')
+  )
 
   objects = DatasetManager()
 
@@ -1753,23 +1845,27 @@ class Dataset(models.Model):
 class DataInput(models.Model):
   name = models.CharField(max_length=40, validators=[name_validator], verbose_name=_t('Name of an input variable in the workflow.'),
                           help_text=_t('The name of the variable of the workflow to automatically fill up.'))
-  dataset = models.OneToOneField(Dataset, verbose_name=_t('The dataset representing format of the data input.'),
+  dataset = models.OneToOneField(Dataset, on_delete=models.CASCADE, verbose_name=_t('The dataset representing format of the data input.'),
                                  help_text=_t('The pattern of the input data we want to process.'))
-  coordinator = models.ForeignKey(Coordinator)
+  coordinator = models.ForeignKey(Coordinator, on_delete=models.CASCADE)
 
 
 class DataOutput(models.Model):
   name = models.CharField(max_length=40, validators=[name_validator], verbose_name=_t('Name of an output variable in the workflow'),
                           help_text=_t('The name of the variable of the workflow to automatically filled up.'))
-  dataset = models.OneToOneField(Dataset, verbose_name=_t('The dataset representing the format of the data output.'),
-                                 help_text=_t('The pattern of the output data we want to generate.'))
-  coordinator = models.ForeignKey(Coordinator)
+  dataset = models.OneToOneField(
+    Dataset,
+    on_delete=models.CASCADE,
+    verbose_name=_t('The dataset representing the format of the data output.'),
+    help_text=_t('The pattern of the output data we want to generate.')
+  )
+  coordinator = models.ForeignKey(Coordinator, on_delete=models.CASCADE)
 
 
 class BundledCoordinator(models.Model):
-  bundle = models.ForeignKey('Bundle', verbose_name=_t('Bundle'),
+  bundle = models.ForeignKey('Bundle', on_delete=models.CASCADE, verbose_name=_t('Bundle'),
                              help_text=_t('The bundle regrouping all the coordinators.'))
-  coordinator = models.ForeignKey(Coordinator, verbose_name=_t('Coordinator'),
+  coordinator = models.ForeignKey(Coordinator, on_delete=models.CASCADE, verbose_name=_t('Coordinator'),
                                   help_text=_t('The coordinator to batch with other coordinators.'))
 
   parameters = models.TextField(default='[{"name":"oozie.use.system.libpath","value":"true"}]', verbose_name=_t('Parameters'),
@@ -1901,10 +1997,10 @@ class History(models.Model):
   """
   Contains information on submitted workflows/coordinators.
   """
-  submitter = models.ForeignKey(User, db_index=True)
+  submitter = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
   submission_date = models.DateTimeField(auto_now=True, db_index=True)
   oozie_job_id = models.CharField(max_length=128)
-  job = models.ForeignKey(Job, db_index=True)
+  job = models.ForeignKey(Job, on_delete=models.CASCADE, db_index=True)
   properties = models.TextField()
 
   objects = HistoryManager()

+ 7 - 1
apps/pig/src/pig/models.py

@@ -33,7 +33,13 @@ from useradmin.models import User
 
 
 class Document(models.Model):
-  owner = models.ForeignKey(User, db_index=True, verbose_name=_t('Owner'), help_text=_t('User who can modify the job.'))
+  owner = models.ForeignKey(
+    User,
+    on_delete=models.CASCADE,
+    db_index=True,
+    verbose_name=_t('Owner'),
+    help_text=_t('User who can modify the job.')
+  )
   is_design = models.BooleanField(default=True, db_index=True, verbose_name=_t('Is a user document, not a document submission.'),
                                      help_text=_t('If the document is not a submitted job but a real query, script, workflow.'))
 

+ 28 - 17
apps/search/src/search/models.py

@@ -102,10 +102,11 @@ class Facet(models.Model):
 
         date_facets = tuple([
            ('facet.date', field_facet['field']),
-           ('f.%s.facet.date.start' % field_facet['field'], 'NOW-%(frequency)s%(unit)s/%(rounder)s' % {"frequency": start["frequency"], "unit": start["unit"], "rounder": gap["unit"]}),
+           ('f.%s.facet.date.start' % field_facet['field'], 'NOW-%(frequency)s%(unit)s/%(rounder)s' % {"frequency": start["frequency"],
+            "unit": start["unit"], "rounder": gap["unit"]}),
            ('f.%s.facet.date.end' % field_facet['field'], 'NOW-%(frequency)s%(unit)s' % end),
-           ('f.%s.facet.date.gap' % field_facet['field'], '+%(frequency)s%(unit)s' % gap),]
-        )
+           ('f.%s.facet.date.gap' % field_facet['field'], '+%(frequency)s%(unit)s' % gap),
+        ])
         params += date_facets
 
     return params
@@ -237,11 +238,19 @@ class Collection(models.Model):
       help_text=_t('Hue properties (e.g. results by pages number)')
   )
 
-  facets = models.ForeignKey(Facet)
-  result = models.ForeignKey(Result)
-  sorting = models.ForeignKey(Sorting)
-
-  owner = models.ForeignKey(User, db_index=True, verbose_name=_t('Owner'), help_text=_t('User who created the job.'), default=None, null=True)
+  facets = models.ForeignKey(Facet, on_delete=models.CASCADE)
+  result = models.ForeignKey(Result, on_delete=models.CASCADE)
+  sorting = models.ForeignKey(Sorting, on_delete=models.CASCADE)
+
+  owner = models.ForeignKey(
+    User,
+    on_delete=models.CASCADE,
+    db_index=True,
+    verbose_name=_t('Owner'),
+    help_text=_t('User who created the job.'),
+    default=None,
+    null=True
+  )
 
   _ATTRIBUTES = ['collection', 'layout', 'autocomplete']
   ICON = 'search/art/icon_search_48.png'
@@ -316,7 +325,9 @@ class Collection(models.Model):
       id_field = id_field[0]
 
     TEMPLATE = {
-      "extracode": escape("<style type=\"text/css\">\nem {\n  font-weight: bold;\n  background-color: yellow;\n}</style>\n\n<script>\n</script>"),
+      "extracode": escape(
+        "<style type=\"text/css\">\nem {\n  font-weight: bold;\n  background-color: yellow;\n}</style>\n\n<script>\n</script>"
+      ),
       "highlighting": [""],
       "properties": {"highlighting_enabled": True},
       "template": """
@@ -417,16 +428,16 @@ class Collection(models.Model):
     props['collection']['template']['extracode'] = escape(self.result.get_extracode())
     props['collection']['template']['isGridLayout'] = False
     props['layout'] = [
-          {"size":2,"rows":[{"widgets":[]}],"drops":["temp"],"klass":"card card-home card-column span2"},
-          {"size":10,"rows":[{"widgets":[
-              {"size":12,"name":"Grid Results","id":"52f07188-f30f-1296-2450-f77e02e1a5c0","widgetType":"html-resultset-widget",
-               "properties":{},"offset":0,"isLoading":True,"klass":"card card-widget span12"}]
-          }], "drops":["temp"],"klass":"card card-home card-column span10"}
+          {"size": 2, "rows": [{"widgets": []}], "drops": ["temp"], "klass": "card card-home card-column span2"},
+          {"size": 10, "rows": [{"widgets": [
+              {"size": 12, "name": "Grid Results", "id": "52f07188-f30f-1296-2450-f77e02e1a5c0", "widgetType": "html-resultset-widget",
+               "properties": {}, "offset": 0, "isLoading": True, "klass": "card card-widget span12"}]
+          }], "drops": ["temp"], "klass": "card card-home card-column span10"}
      ]
 
     from search.views import _create_facet
 
-    props['collection']['facets'] =[]
+    props['collection']['facets'] = []
     facets = self.facets.get_data()
 
     for facet_id in facets['order']:
@@ -435,6 +446,6 @@ class Collection(models.Model):
           props['collection']['facets'].append(
               _create_facet({'name': self.name}, user, facet_id, facet['label'], facet['field'], 'facet-widget'))
           props['layout'][0]['rows'][0]['widgets'].append({
-              "size":12,"name": facet['label'], "id":facet_id, "widgetType": "facet-widget",
-              "properties":{},"offset":0,"isLoading":True,"klass":"card card-widget span12"
+              "size": 12, "name": facet['label'], "id": facet_id, "widgetType": "facet-widget",
+              "properties": {}, "offset": 0, "isLoading": True, "klass": "card card-widget span12"
           })

+ 1 - 1
apps/useradmin/src/useradmin/models.py

@@ -82,7 +82,7 @@ class UserProfile(models.Model):
     HUE = 1
     EXTERNAL = 2
 
-  user = models.OneToOneField(User, unique=True)
+  user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True)
   home_directory = models.CharField(editable=True, max_length=1024, null=True)
   creation_method = models.CharField(editable=True, null=False, max_length=64, default=CreationMethod.HUE.name)
   first_login = models.BooleanField(default=True, verbose_name=_t('First Login'), help_text=_t('If this is users first login.'))

+ 6 - 6
apps/useradmin/src/useradmin/permissions.py

@@ -37,15 +37,15 @@ class LdapGroup(models.Model):
   Groups that come from LDAP originally will have an LdapGroup
   record generated at creation time.
   """
-  group = models.ForeignKey(Group, related_name="group")
+  group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name="group")
 
 
 class GroupPermission(models.Model):
   """
   Represents the permissions a group has.
   """
-  group = models.ForeignKey(Group)
-  hue_permission = models.ForeignKey("HuePermission")
+  group = models.ForeignKey(Group, on_delete=models.CASCADE)
+  hue_permission = models.ForeignKey("HuePermission", on_delete=models.CASCADE)
 
 
 class BasePermission(models.Model):
@@ -53,8 +53,8 @@ class BasePermission(models.Model):
   Set of non-object specific permissions that an app supports.
 
   Currently only assign permissions to groups (not users or roles).
-  Could someday support external permissions of Apache Ranger permissions, AWS IAM... This could be done via subclasses or creating new types
-  of connectors.
+  Could someday support external permissions of Apache Ranger permissions, AWS IAM...
+  This could be done via subclasses or creating new types of connectors.
   """
   app = models.CharField(max_length=30)
   action = models.CharField(max_length=100)
@@ -74,7 +74,7 @@ class BasePermission(models.Model):
 
 
 class ConnectorPermission(BasePermission):
-  connector = models.ForeignKey(Connector)
+  connector = models.ForeignKey(Connector, on_delete=models.CASCADE)
 
   class Meta(object):
     abstract = True

+ 16 - 8
desktop/core/src/desktop/models.py

@@ -133,7 +133,7 @@ class UserPreferences(models.Model):
   Holds arbitrary key/value strings.
   Note: ideally user/jkeu should be unique together.
   """
-  user = models.ForeignKey(User)
+  user = models.ForeignKey(User, on_delete=models.CASCADE)
   key = models.CharField(max_length=20)
   value = models.TextField(max_length=4096)
 
@@ -183,7 +183,7 @@ class DefaultConfiguration(models.Model):
 
   is_default = models.BooleanField(default=False, db_index=True)
   groups = models.ManyToManyField(Group, db_index=True, db_table='defaultconfiguration_groups')
-  user = models.ForeignKey(User, blank=True, null=True, db_index=True)
+  user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, db_index=True)
 
   objects = DefaultConfigurationManager()
 
@@ -307,7 +307,7 @@ class DocumentTag(models.Model):
   """
   Reserved tags can't be manually removed by the user.
   """
-  owner = models.ForeignKey(User, db_index=True)
+  owner = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
   tag = models.SlugField()
 
   DEFAULT = 'default' # Always there
@@ -637,7 +637,7 @@ class DocumentManager(models.Manager):
 
 class Document(models.Model):
 
-  owner = models.ForeignKey(User, db_index=True, verbose_name=_t('Owner'),
+  owner = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True, verbose_name=_t('Owner'),
                             help_text=_t('User who can own the job.'), related_name='doc_owner')
   name = models.CharField(default='', max_length=255)
   description = models.TextField(default='')
@@ -648,7 +648,7 @@ class Document(models.Model):
 
   tags = models.ManyToManyField(DocumentTag, db_index=True)
 
-  content_type = models.ForeignKey(ContentType)
+  content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
   object_id = models.PositiveIntegerField()
   content_object = GenericForeignKey('content_type', 'object_id')
 
@@ -901,7 +901,7 @@ class DocumentPermission(models.Model):
   READ_PERM = 'read'
   WRITE_PERM = 'write'
 
-  doc = models.ForeignKey(Document)
+  doc = models.ForeignKey(Document, on_delete=models.CASCADE)
 
   users = models.ManyToManyField(User, db_index=True, db_table='documentpermission_users')
   groups = models.ManyToManyField(Group, db_index=True, db_table='documentpermission_groups')
@@ -1133,7 +1133,14 @@ class Document2(models.Model):
   TRASH_DIR = '.Trash'
   EXAMPLES_DIR = 'examples'
 
-  owner = models.ForeignKey(User, db_index=True, verbose_name=_t('Owner'), help_text=_t('Creator.'), related_name='doc2_owner')
+  owner = models.ForeignKey(
+    User,
+    on_delete=models.CASCADE,
+    db_index=True,
+    verbose_name=_t('Owner'),
+    help_text=_t('Creator.'),
+    related_name='doc2_owner'
+  )
   name = models.CharField(default='', max_length=255)
   description = models.TextField(default='')
   uuid = models.CharField(default=uuid_default, max_length=36, db_index=True)
@@ -1145,6 +1152,7 @@ class Document2(models.Model):
   )
   connector = models.ForeignKey(
       Connector,
+      on_delete=models.CASCADE,
       verbose_name=_t('Connector'),
       help_text=_t('Connector.'),
       blank=True,
@@ -1652,7 +1660,7 @@ class Document2Permission(models.Model):
   LINK_READ_PERM = 'link_read'
   LINK_WRITE_PERM = 'link_write'
 
-  doc = models.ForeignKey(Document2)
+  doc = models.ForeignKey(Document2, on_delete=models.CASCADE)
 
   users = models.ManyToManyField(User, db_index=True, db_table='documentpermission2_users')
   groups = models.ManyToManyField(Group, db_index=True, db_table='documentpermission2_groups')