@login_required
def get_possible_parameters(request, workflow_task, data_type="string"):
    params = [('prompted.string.askme', '--Ask Me--')]
    user = request.user
    user_params = UserParameter.objects.all().filter(owner=user, data_type=data_type)
    org_params = OrganizationParameter.objects.all().filter(owner=workflow_task.workflow.owner, data_type=data_type)

    for param in user_params:
        params.append(("user.%s.%s" % (param.data_type, param.name), "user: %s" % param.name))

    for param in org_params:
        params.append(("global.%s.%s" % (param.data_type, param.name), "team: %s" % param.name))

    current = workflow_task
    # append all outputs from previous workflow tasks
    while current.prev_workflow_task:
        current = current.prev_workflow_task
        params.append(("output.string.output_%s" % current.id, "output: %s" % current.task.title))

    return tuple(params) @login_required
def get_possible_worker(request, as_list=False):
    orgs = get_my_orgs(request)
    if as_list:
        return Worker.objects.filter(owner__in=orgs).values_list('id', 'title')
    return Worker.objects.filter(owner__in=orgs) @login_required
def get_curr_org(request):
    org = Organization.objects.get(slug="3bot")  # request.user)
    request.session['currOrg'] = org.slug
    request.session.modified = True
    return org @login_required
def get_preset_param(request, workflow_task, name, data_type="string"):
    # search the workflowpreset profile for the variable
    wf_preset, created = WorkflowPreset.objects.get_or_create(user=request.user, workflow=workflow_task.workflow)
    try:
        return wf_preset.defaults[str(workflow_task.id)]["%s.%s" % (data_type, name)]
    except KeyError:
        return None @login_required
def get_preset_worker(request, workflow, flat=False):
    """
    returns a Worker queryset or a list of ids (flat = True),
    which a workflow recently was performed on by request.user.
    """
    # find or create the workflowpreset
    wf_preset, created = WorkflowPreset.objects.get_or_create(user=request.user, workflow=workflow)

    try:
        worker_ids = wf_preset.defaults["worker_id"]
    except KeyError as e:
        worker_ids = []

    # backwards compatibility
    if isinstance(worker_ids, int) or isinstance(worker_ids, str):
        worker_ids = [worker_ids]

    if flat:
        return worker_ids

    return Worker.objects.filter(id__in=worker_ids) @login_required
def get_possible_owners(request):
    return get_my_orgs(request) def get_possible_lists(request, workflow):
    return ParameterList.objects.all().filter(owner=workflow.owner).values_list('id', 'title').order_by('title') @login_required
def get_preset_list(request, workflow, id=False):
    # search the workflowpreset
    wf_preset, created = WorkflowPreset.objects.get_or_create(user=request.user, workflow=workflow)
    try:
        list_id = wf_preset.defaults["list_id"]
        if id:
            return list_id
        return ParameterList.objects.get(id=list_id)
    except (KeyError, Worker.DoesNotExist):
        pass
    return None class Worker(models.Model):
    ACCESS_REQUEST_TIMEOUT = 1
    STATUS_OFFLINE = 0
    STATUS_ONLINE = 1
    STATUS_BUSY = 9
    STATUS_MUTED = 10
    STATUS_CHOICES = (
        (STATUS_OFFLINE, 'offline'),
        (STATUS_ONLINE, 'online'),
        (STATUS_BUSY, 'busy'),
        (STATUS_MUTED, 'muted'),
    )

    owner = models.ForeignKey(Organization, help_text="Worker owner")
    title = models.CharField(max_length=255)
    slug = models.SlugField(_('slug'), max_length=50, unique=True)

    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

    ip = models.GenericIPAddressField("IP-Address",)
    addr = models.CharField(max_length=200, null=True, blank=True, help_text='For now we use the IP to connect to the worker')
    port = models.PositiveIntegerField(default=55555)
    secret_key = models.CharField(max_length=200, null=True, blank=True, help_text='The Secret Key. Never share yours.')

    muted = models.BooleanField(default=False, help_text="Mute a Worker to prevent accessibility checks and improve performance.")

    pre_task = models.TextField(null=True, blank=True, help_text='this will run as a Script before the worker performs a workflow')
    post_task = models.TextField(null=True, blank=True, help_text='this will run as a Script after the worker has performed a workflow')

    class Meta():
        ordering = ['owner', 'title']
        verbose_name = _("Worker")
        verbose_name_plural = _("Workers")

    def get_status_display(self):
        choices = dict(self.STATUS_CHOICES)
        return choices[self.status]

    @cached_property
    def status(self):
        if self.muted:
            return self.STATUS_MUTED
        if self.is_busy:
            return self.STATUS_BUSY
        if self.is_accessible:
            return self.STATUS_ONLINE
        else:
            return self.STATUS_OFFLINE

    @cached_property
    def is_busy(self):
        """Identifies is a Worfkflow is currently blocking this worker."""
        return WorkflowLog.objects.filter(exit_code=WorkflowLog.PENDING, performed_on=self).exists()

    @cached_property
    def is_accessible(self):
        if self.muted:
            return False

        try:
            from .botconnection import BotConnection
            from .tasks import send_script

            protocol = "tcp"
            WORKER_ENDPOINT = "%s://%s:%s" % (protocol, self.ip, str(self.port))
            WORKER_SECRET_KEY = str(self.secret_key)
            conn = BotConnection(WORKER_ENDPOINT, WORKER_SECRET_KEY)
            conn.connect()

            req = {'type': 'ACC'}
            resp = send_script(req, conn, REQUEST_TIMEOUT=1000, REQUEST_RETRIES=1)
            conn.close()

            if resp and resp['type'] == 'ACK':
                return True
        except Exception as e:
            logger.error(e, exc_info=True)
        return False

    def save(self, *args, **kwargs):
        if not self.pk:  # first save
            self.slug = random.randint(1000, 9999)
            super(Worker, self).save(*args, **kwargs)

        s = str(self.owner.id) + "-" + str(self.id) + "-" + slugify(self.title)
        self.slug = s[:50]
        super(Worker, self).save(*args, **kwargs)

    @models.permalink
    def get_absolute_url(self):
        return ('core_worker_detail', (), {
            'slug': self.slug})

    def permalink(self):
        return reverse('core_worker_permalink', kwargs={'id': self.id})

    def __str__(self):
        return self.title class Workflow(models.Model):
    unique_identifier = models.CharField(max_length=255, null=True, blank=True, help_text="Unique Identifier to group multiple Workflow Versions")
    version_major = models.PositiveIntegerField(null=True, blank=True, default=0)
    version_minor = models.PositiveIntegerField(null=True, blank=True, default=0)

    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

    owner = models.ForeignKey(Organization, help_text="Workflow owner")
    title = models.CharField(max_length=255)
    slug = models.SlugField(_('slug'), max_length=50, unique=True)
    desc = models.TextField(null=True, blank=True)

    pre_task = models.TextField(null=True, blank=True, help_text='This will run as a Script before the worker runs a task of the current workflow. (Runs before each task)')
    post_task = models.TextField(null=True, blank=True, help_text='This will run as a Script after the worker has executed a task of the current workflow. (Runs after each task)')

    tasks = models.ManyToManyField(Task, through='WorkflowTask')

    class Meta():
        ordering = ['owner', 'title']
        verbose_name = _("Workflow")
        verbose_name_plural = _("Workflows")
        unique_together = (('unique_identifier', 'version_major', 'version_minor'),)

    def number_of_tasks(self):
        return WorkflowTask.objects.filter(workflow=self).count()

    def save(self, *args, **kwargs):
        if not self.pk:  # first save
            self.slug = random.randint(1000, 9999)
            super(Workflow, self).save(*args, **kwargs)

        if not self.unique_identifier:
            md_hash = hashlib.md5()
            md_hash.update(self.title.encode('utf-8') + str(datetime.datetime.now()))
            self.unique_identifier = md_hash.hexdigest()

        s = str(self.owner_id) + "-" + str(self.id) + "-" + slugify(self.title)
        self.slug = s[:50]
        super(Workflow, self).save(*args, **kwargs)

    @models.permalink
    def get_absolute_url(self):
        return ('core_workflow_detail', (), {
            'slug': self.slug})

    def permalink(self):
        return reverse('core_workflow_permalink', kwargs={'id': self.id})

    def __str__(self):
        return self.title or self.desc or str(self.pk) class Task(models.Model):
    unique_identifier = models.CharField(max_length=255, null=True, blank=True, help_text="Unique Identifier to group multiple Task Versions")
    version_major = models.PositiveIntegerField(null=True, blank=True, default=0)
    version_minor = models.PositiveIntegerField(null=True, blank=True, default=0)

    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

    is_readonly = models.BooleanField(default=False, help_text="Only Team-Admins can edit")
    is_builtin = models.BooleanField(default=False, help_text="Will be executed as Python Module on 3bot Platform")

    owner = models.ForeignKey(Organization, help_text="Task owner")
    title = models.CharField(max_length=255)
    slug = models.SlugField(_('slug'), max_length=50, unique=True)
    desc = models.TextField(null=True, blank=True)

    template = models.TextField(default="#!/bin/sh\n", help_text="To declare an input, type: {{ <data type>.<identifier> }}")

    inputs_fingerprint = models.CharField(max_length=32, blank=True, null=True)
    changelog = models.TextField(blank=True, null=True)

    class Meta():
        ordering = ['owner', 'title']
        verbose_name = _("Task")
        verbose_name_plural = _("Tasks")
        unique_together = (('unique_identifier', 'version_major', 'version_minor'),)

    def clean(self):
        valid_data_types = 'Valid data types are: '
        for el in Parameter.DATA_TYPE_CHOICES:
            valid_data_types += '"%s", ' % el[0]

        valid_identifier = VALID_IDENTIFIER

        inputs = self.extract_inputs()

        if not inputs:
            return

        for name, data_type in inputs.iteritems():
            field = 'template'
            if not any(data_type == el[0] for el in Parameter.DATA_TYPE_CHOICES):
                msg = '"%s" is not a valid data type' % data_type
                raise ValidationError({field: [msg, valid_data_types, ]})
            if not is_valid_identifier(name):
                msg = '"%s" is not a valid identifier' % name
                raise ValidationError({field: [msg, valid_identifier, ]})

    def extract_inputs(self):
        """returns a dict of variables declared in the tempalte string"""
        """ {u'some_words': u'string', u'some_word': u'string'} """
        found_inputs = re.findall(r'\{{(.+?)\}}', self.template)
        inputs = {}
        field = 'template'

        for i in found_inputs:
            msg = '"%s" is not a valid input declaration.' % str(i.encode('utf-8').strip())
            inp = i.strip().split(".")
            if inp[0] not in RESERVED_IDENTIFIERS:
                try:
                    if not inp[0] or not inp[1]:
                        raise ValidationError({field: [msg, ]})
                    inputs[inp[1]] = inp[0]
                except IndexError:
                    raise ValidationError({field: [msg, ]})

        return inputs

    @property
    def required_inputs(self):
        return self.extract_inputs()

    @property
    def type(self):
        known_types = ['bash', 'sh', 'python', 'ruby', 'pearl', 'php', 'node', 'osascript']

        if self.is_builtin:
            return "built-in"
        if self.template.startswith('#!'):
            shebang = self.template.split('\n', 1)[0]
            for type in known_types:
                if shebang.find(type) > 0:
                    return type
        return "unknown"

    @property
    def version(self):
        return "%i.%i" % (self.version_major, self.version_minor)

    def save(self, *args, **kwargs):
        if not self.pk:  # first save
            self.slug = random.randint(1000, 9999)
            super(Task, self).save(*args, **kwargs)

        if not self.unique_identifier:
            md_hash = hashlib.md5()
            md_hash.update(self.title.encode('utf-8') + str(datetime.datetime.now()))
            self.unique_identifier = md_hash.hexdigest()

        s = str(self.owner.id) + "-" + str(self.id) + "-" + slugify(self.title)
        self.slug = s[:50]
        fingerprint = hashlib.md5()
        fingerprint.update(str(self.required_inputs).encode('utf-8'))
        self.inputs_fingerprint = fingerprint.hexdigest()
        super(Task, self).save(*args, **kwargs)

    @models.permalink
    def get_absolute_url(self):
        return ('core_task_detail', (), {
            'slug': self.slug})

    def permalink(self):
        return reverse('core_task_permalink', kwargs={'id': self.id})

    def __str__(self):
        return self.title or self.desc or str(self.pk) class UserParameter(Parameter):
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, help_text="Parameter owner")

    class Meta():
        ordering = ['owner', 'data_type', ]
        verbose_name = _("Parameter for User")
        verbose_name_plural = _("Parameters for Users")

    def save(self, *args, **kwargs):
        super(UserParameter, self).save(*args, **kwargs)

    def validate_unique(self, *args, **kwargs):
        # this is called every time the obj gets updated or saved
        # the try/except is needed to prevent DoesNotExist errors for the owner field.
        try:
            self.owner
        except:
            return
        # Checks that no two parameters have the same name, data_type and owner
        if UserParameter.objects.filter(name=self.name, data_type=self.data_type, owner=self.owner).exists():
            same_param = UserParameter.objects.get(name=self.name, data_type=self.data_type, owner=self.owner)
            if not same_param.id == self.id:
                raise ValidationError(
                    {
                        NON_FIELD_ERRORS:
                        ('Parameter with this name and data type already exists',)
                    }
                )

    def __str__(self):
        return "%s:%s (%s)" % (self.data_type, self.name, self.owner)

    @models.permalink
    def get_absolute_url(self):
        return ('core_user_parameter_detail', (), {
            'id': self.id}) class OrganizationParameter(Parameter):
    owner = models.ForeignKey(Organization, help_text="Parameter owner")

    class Meta():
        ordering = ['owner', 'data_type', ]
        verbose_name = _("Parameter for Team")
        verbose_name_plural = _("Parameters for Team")

    def save(self, *args, **kwargs):
        super(OrganizationParameter, self).save(*args, **kwargs)

    def validate_unique(self, *args, **kwargs):
        # this is called every time the obj gets updated or saved
        # the try/except is needed to prevent DoesNotExist errors for the owner field.
        try:
            self.owner
        except:
            return
        # Checks that no two parameters have the same name, data_type and owner
        if OrganizationParameter.objects.filter(name=self.name, data_type=self.data_type, owner=self.owner).exists():
            same_param = OrganizationParameter.objects.get(name=self.name, data_type=self.data_type, owner=self.owner)
            if not same_param.id == self.id:
                raise ValidationError(
                    {
                        NON_FIELD_ERRORS:
                        ('Parameter with this name and data type already exists',)
                    }
                )

    def __str__(self):
        return "%s:%s (%s)" % (self.data_type, self.name, self.owner)

    @models.permalink
    def get_absolute_url(self):
        return ('core_organization_parameter_detail', (), {
            'slug': self.owner.slug,
            'id': self.id}) class ParameterList(models.Model):
    """
    ParameterList
    """
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

    title = models.CharField(max_length=255)
    owner = models.ForeignKey(Organization, help_text="Parameter List owner")
    parameters = models.ManyToManyField(OrganizationParameter, related_name="lists", help_text="Select Parameters for this Team.")

    class Meta():
        ordering = ['title', 'date_created', ]
        verbose_name = _("Parameter List")
        verbose_name_plural = _("Parameter Lists")

    def __str__(self):
        return self.title def order_workflow_tasks(workflow):
    try:
        curr = workflow.workflowtask_set.get(prev_workflow_task=None)
        workflow_tasks = [curr]
        while curr.next_workflow_task:
            curr = curr.next_workflow_task
            workflow_tasks.append(curr)
    except WorkflowTask.DoesNotExist:
        workflow_tasks = []

    return workflow_tasks
from django import forms
from django.forms.models import modelformset_factory
from django.utils.safestring import mark_safe
from organizations.models import Organization
from organizations.utils import create_organization
from threebot.utils import get_possible_parameters, get_possible_worker, get_curr_org
from threebot.utils import get_preset_param, get_preset_worker, get_possible_owners, get_possible_lists, get_preset_list
from threebot.models import Worker
from threebot.models import Workflow
from threebot.models import Task
from threebot.models import UserParameter, OrganizationParameter, ParameterList
from threebot.utils import order_workflow_tasks
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control', }),
            'ip': forms.TextInput(attrs={'class': 'form-control', }),
            'addr': forms.TextInput(attrs={'class': 'form-control', 'rows': '5', }),
            'port': forms.TextInput(attrs={'class': 'form-control', }),
            'secret_key': forms.TextInput(attrs={'class': 'form-control', }),
            'pre_task': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', }),
            'post_task': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', }),
        }

    def __init__(self, *args, **kwargs):
        super(WorkerForm, self).__init__(*args, **kwargs)


class WorkerCreateForm(WorkerForm):
    """Form to create a Worker"""
    class Meta(WorkerForm.Meta):
        fields = ['owner', ] + WorkerForm.Meta.fields
        exclude = ['muted']
        WorkerForm.Meta.widgets['owner'] = forms.Select(attrs={'class': 'form-control'})

    def __init__(self, *args, **kwargs):
        request = kwargs.pop('request')
        super(WorkerCreateForm, self).__init__(*args, **kwargs)
        self.fields['owner'].queryset = get_possible_owners(request)
        self.fields['owner'].initial = get_curr_org(request)


class WorkerChangeForm(WorkerForm):
    """Form to edit a Worker"""
    pass


class TaskForm(forms.ModelForm):
    """Base Task Form"""
    class Meta:
        model = Task
        fields = ['title', 'desc', 'template', 'changelog', 'is_readonly', ]
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control', }),
            'desc': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', }),
            'template': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', 'style': 'font-family:monospace;'}),
            'changelog': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', }),
            'is_readonly': forms.CheckboxInput(attrs={}),
        }

    def __init__(self, *args, **kwargs):
        super(TaskForm, self).__init__(*args, **kwargs)


class TaskCreateForm(TaskForm):
    """Form to create a Task"""
    class Meta(TaskForm.Meta):
        fields = ['owner', ] + TaskForm.Meta.fields
        TaskForm.Meta.widgets['owner'] = forms.Select(attrs={'class': 'form-control'})

    def __init__(self, *args, **kwargs):
        request = kwargs.pop('request')
        super(TaskCreateForm, self).__init__(*args, **kwargs)
        self.fields['owner'].queryset = get_possible_owners(request)
        self.fields['owner'].initial = get_curr_org(request)


class TaskChangeForm(TaskForm):
    """Form to edit a Task"""
    pass


class TaskImportForm(forms.Form):
    """Form to import a Task"""
    task_json = forms.FileField()


class WorkflowForm(forms.ModelForm):
    """Base Workflow Form"""
    class Meta:
        model = Workflow
        fields = ['title', 'desc', 'pre_task', 'post_task', ]
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-control', }),
            'desc': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', }),
            'pre_task': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', }),
            'post_task': forms.Textarea(attrs={'class': 'form-control', 'rows': '5', }),
        }

    def __init__(self, *args, **kwargs):
        super(WorkflowForm, self).__init__(*args, **kwargs)


class WorkflowCreateForm(WorkflowForm):
    """Form to create a Workflow"""
    class Meta(WorkflowForm.Meta):
        fields = ['owner', ] + WorkflowForm.Meta.fields
        WorkflowForm.Meta.widgets['owner'] = forms.Select(attrs={'class': 'form-control'})

    def __init__(self, *args, **kwargs):
        request = kwargs.pop('request')
        super(WorkflowCreateForm, self).__init__(*args, **kwargs)
        self.fields['owner'].queryset = get_possible_owners(request)
        self.fields['owner'].initial = get_curr_org(request)


class WorkflowChangeForm(WorkflowForm):
    """Form to edit a Workflow"""
    pass


class WorkflowReorderForm(forms.Form):
    workflow_id = forms.CharField(max_length=30, widget=forms.HiddenInput())
    order = forms.CharField(widget=forms.HiddenInput())


class TaskParameterForm(forms.Form):
    def __init__(self, *args, **kwargs):
        request = kwargs.pop('request')
        extra = kwargs.pop('extra')
        workflow_task = kwargs.pop('workflow_task')
        super(TaskParameterForm, self).__init__(*args, **kwargs)

        for name, data_type in extra.iteritems():

            possible_parameters = get_possible_parameters(request, workflow_task, data_type)