-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial work on custom scripts (#3415)
- Loading branch information
1 parent
f18c3be
commit a25a27f
Showing
11 changed files
with
376 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
from collections import OrderedDict | ||
import inspect | ||
import pkgutil | ||
|
||
from django import forms | ||
from django.conf import settings | ||
|
||
from .constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING | ||
from .forms import ScriptForm | ||
|
||
|
||
# | ||
# Script variables | ||
# | ||
|
||
class ScriptVariable: | ||
form_field = forms.CharField | ||
|
||
def __init__(self, label='', description=''): | ||
|
||
# Default field attributes | ||
if not hasattr(self, 'field_attrs'): | ||
self.field_attrs = {} | ||
if label: | ||
self.field_attrs['label'] = label | ||
if description: | ||
self.field_attrs['help_text'] = description | ||
|
||
def as_field(self): | ||
""" | ||
Render the variable as a Django form field. | ||
""" | ||
return self.form_field(**self.field_attrs) | ||
|
||
|
||
class StringVar(ScriptVariable): | ||
pass | ||
|
||
|
||
class IntegerVar(ScriptVariable): | ||
form_field = forms.IntegerField | ||
|
||
|
||
class BooleanVar(ScriptVariable): | ||
form_field = forms.BooleanField | ||
field_attrs = { | ||
'required': False | ||
} | ||
|
||
|
||
class ObjectVar(ScriptVariable): | ||
form_field = forms.ModelChoiceField | ||
|
||
def __init__(self, queryset, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
|
||
self.field_attrs['queryset'] = queryset | ||
|
||
|
||
class Script: | ||
""" | ||
Custom scripts inherit this object. | ||
""" | ||
|
||
def __init__(self): | ||
|
||
# Initiate the log | ||
self.log = [] | ||
|
||
# Grab some info about the script | ||
self.filename = inspect.getfile(self.__class__) | ||
self.source = inspect.getsource(self.__class__) | ||
|
||
def __str__(self): | ||
if hasattr(self, 'name'): | ||
return self.name | ||
return self.__class__.__name__ | ||
|
||
def _get_vars(self): | ||
# TODO: This should preserve var ordering | ||
return inspect.getmembers(self, is_variable) | ||
|
||
def run(self, context): | ||
raise NotImplementedError("The script must define a run() method.") | ||
|
||
def as_form(self, data=None): | ||
""" | ||
Return a Django form suitable for populating the context data required to run this Script. | ||
""" | ||
vars = self._get_vars() | ||
form = ScriptForm(vars, data) | ||
|
||
return form | ||
|
||
# Logging | ||
|
||
def log_debug(self, message): | ||
self.log.append((LOG_DEFAULT, message)) | ||
|
||
def log_success(self, message): | ||
self.log.append((LOG_SUCCESS, message)) | ||
|
||
def log_info(self, message): | ||
self.log.append((LOG_INFO, message)) | ||
|
||
def log_warning(self, message): | ||
self.log.append((LOG_WARNING, message)) | ||
|
||
def log_failure(self, message): | ||
self.log.append((LOG_FAILURE, message)) | ||
|
||
|
||
# | ||
# Functions | ||
# | ||
|
||
def is_script(obj): | ||
""" | ||
Returns True if the object is a Script. | ||
""" | ||
return obj in Script.__subclasses__() | ||
|
||
|
||
def is_variable(obj): | ||
""" | ||
Returns True if the object is a ScriptVariable. | ||
""" | ||
return isinstance(obj, ScriptVariable) | ||
|
||
|
||
def get_scripts(): | ||
scripts = OrderedDict() | ||
|
||
# Iterate through all modules within the reports path. These are the user-created files in which reports are | ||
# defined. | ||
for importer, module_name, _ in pkgutil.iter_modules([settings.SCRIPTS_ROOT]): | ||
module = importer.find_module(module_name).load_module(module_name) | ||
module_scripts = OrderedDict() | ||
for name, cls in inspect.getmembers(module, is_script): | ||
module_scripts[name] = cls | ||
scripts[module_name] = module_scripts | ||
|
||
return scripts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from django import template | ||
|
||
from extras.constants import LOG_DEFAULT, LOG_FAILURE, LOG_INFO, LOG_SUCCESS, LOG_WARNING | ||
|
||
|
||
register = template.Library() | ||
|
||
|
||
@register.inclusion_tag('extras/templatetags/log_level.html') | ||
def log_level(level): | ||
""" | ||
Display a label indicating a syslog severity (e.g. info, warning, etc.). | ||
""" | ||
levels = { | ||
LOG_DEFAULT: { | ||
'name': 'Default', | ||
'class': 'default' | ||
}, | ||
LOG_SUCCESS: { | ||
'name': 'Success', | ||
'class': 'success', | ||
}, | ||
LOG_INFO: { | ||
'name': 'Info', | ||
'class': 'info' | ||
}, | ||
LOG_WARNING: { | ||
'name': 'Warning', | ||
'class': 'warning' | ||
}, | ||
LOG_FAILURE: { | ||
'name': 'Failure', | ||
'class': 'danger' | ||
} | ||
} | ||
|
||
return levels[level] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
{% extends '_base.html' %} | ||
{% load helpers %} | ||
{% load form_helpers %} | ||
{% load log_levels %} | ||
|
||
{% block title %}{{ script }}{% endblock %} | ||
|
||
{% block content %} | ||
<div class="row noprint"> | ||
<div class="col-md-12"> | ||
<ol class="breadcrumb"> | ||
<li><a href="{% url 'extras:script_list' %}">Scripts</a></li> | ||
<li><a href="{% url 'extras:script_list' %}#module.{{ module }}">{{ module|bettertitle }}</a></li> | ||
<li>{{ script }}</li> | ||
</ol> | ||
</div> | ||
</div> | ||
<h1>{{ script }}</h1> | ||
<p>{{ script.description }}</p> | ||
<ul class="nav nav-tabs" role="tablist"> | ||
<li role="presentation" class="active"> | ||
<a href="#run" role="tab" data-toggle="tab" class="active">Run</a> | ||
</li> | ||
<li role="presentation"> | ||
<a href="#source" role="tab" data-toggle="tab">Source</a> | ||
</li> | ||
</ul> | ||
<div class="tab-content"> | ||
<div role="tabpanel" class="tab-pane active" id="run"> | ||
{% if script.log %} | ||
<div class="row"> | ||
<div class="col-md-12"> | ||
<div class="panel panel-default"> | ||
<div class="panel-heading"> | ||
<strong>Script Output</strong> | ||
</div> | ||
<table class="table table-hover panel-body"> | ||
<tr> | ||
<th>Line</th> | ||
<th>Level</th> | ||
<th>Message</th> | ||
</tr> | ||
{% for level, message in script.log %} | ||
<tr> | ||
<td>{{ forloop.counter }}</td> | ||
<td>{% log_level level %}</td> | ||
<td>{{ message }}</td> | ||
</tr> | ||
{% endfor %} | ||
</table> | ||
</div> | ||
</div> | ||
</div> | ||
{% endif %} | ||
<div class="row"> | ||
<div class="col-md-8 col-md-offset-2"> | ||
<form action="" method="post"> | ||
{% csrf_token %} | ||
{% if form %} | ||
{% render_form form %} | ||
{% else %} | ||
<p>This script does not require any input to run.</p> | ||
{% endif %} | ||
<div class="pull-right"> | ||
<button type="submit" name="_run" class="btn btn-primary"><i class="fa fa-play"></i> Run Script</button> | ||
<a href="{% url 'extras:script_list' %}" class="btn btn-default">Cancel</a> | ||
</div> | ||
</form> | ||
</div> | ||
</div> | ||
</div> | ||
<div role="tabpanel" class="tab-pane" id="source"> | ||
<strong>{{ script.filename }}</strong> | ||
<pre>{{ script.source }}</pre> | ||
</div> | ||
</div> | ||
{% endblock %} |
Oops, something went wrong.