<commit_before>
import django
from django.conf import settings
from django.contrib.admin.views.main import ChangeList
from django.contrib.admin.options import ModelAdmin, IncorrectLookupParameters
from django import template
from django.contrib.admin import helpers
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.utils.encoding import force_unicode

from mptt.forms import MPTTAdminForm, TreeNodeChoiceField

__all__ = ('MPTTChangeList', 'MPTTModelAdmin', 'MPTTAdminForm')

class MPTTChangeList(ChangeList):
    def get_query_set(self, request=None):
        # request arg was added in django r16144 (after 1.3)
        if request is not None and django.VERSION >= (1, 4):
            qs = super(MPTTChangeList, self).get_query_set(request)
        else:
            qs = super(MPTTChangeList, self).get_query_set()

        # always order by (tree_id, left)
        tree_id = qs.model._mptt_meta.tree_id_attr
        left = qs.model._mptt_meta.left_attr
        return qs.order_by(tree_id, left)

class MPTTModelAdmin(ModelAdmin):
    """
    A basic admin class that displays tree items according to their position in the tree.
    No extra editing functionality beyond what Django admin normally offers.
    """

    change_list_template = 'admin/mptt_change_list.html'

    form = MPTTAdminForm

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        from mptt.models import MPTTModel, TreeForeignKey
        if issubclass(db_field.rel.to, MPTTModel) and not isinstance(db_field, TreeForeignKey):
            defaults = dict(form_class=TreeNodeChoiceField, queryset=db_field.rel.to.objects.all(), required=False)
            defaults.update(kwargs)
            kwargs = defaults
        return super(MPTTModelAdmin, self).formfield_for_foreignkey(db_field,
                                                                    request,
                                                                    **kwargs)

    def get_changelist(self, request, **kwargs):
        """
        Returns the ChangeList class for use on the changelist page.
        """
        return MPTTChangeList

    # In Django 1.1, the changelist class is hard coded in changelist_view, so
    # we've got to override this too, just to get it to use our custom ChangeList
    if django.VERSION < (1, 2):
        def changelist_view(self, request, extra_context=None):
            "The 'change list' admin view for this model."
            from django.contrib.admin.views.main import ERROR_FLAG
            opts = self.model._meta
            app_label = opts.app_label
            if not self.has_change_permission(request, None):
                raise PermissionDenied

            # Check actions to see if any are available on this changelist
            actions = self.get_actions(request)

            # Remove action checkboxes if there aren't any actions available.
            list_display = list(self.list_display)
            if not actions:
                try:
                    list_display.remove('action_checkbox')
                except ValueError:
                    pass

            CL = self.get_changelist(request)

            try:
                cl = CL(request, self.model, list_display, self.list_display_links, self.list_filter,
                    self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self)
            except IncorrectLookupParameters:
                # Wacky lookup parameters were given, so redirect to the main
                # changelist page, without parameters, and pass an 'invalid=1'
                # parameter via the query string. If wacky parameters were given and
                # the 'invalid=1' parameter was already in the query string, something
                # is screwed up with the database, so display an error page.
                if ERROR_FLAG in request.GET.keys():
                    return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
                return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')

            # If the request was POSTed, this might be a bulk action or a bulk edit.
            # Try to look up an action or confirmation first, but if this isn't an
            # action the POST will fall through to the bulk edit check, below.
            if actions and request.method == 'POST' and (helpers.ACTION_CHECKBOX_NAME in request.POST or 'index' in request.POST):
                response = self.response_action(request, queryset=cl.get_query_set(request))
                if response:
                    return response

            # If we're allowing changelist editing, we need to construct a formset
            # for the changelist given all the fields to be edited. Then we'll
            # use the formset to validate/process POSTed data.
            formset = cl.formset = None

            # Handle POSTed bulk-edit data.
            if request.method == "POST" and self.list_editable:
                FormSet = self.get_changelist_formset(request)
                formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
                if formset.is_valid():
                    changecount = 0
                    for form in formset.forms:
                        if form.has_changed():
                            obj = self.save_form(request, form, change=True)
                            self.save_model(request, obj, form, change=True)
                            form.save_m2m()
                            change_msg = self.construct_change_message(request, form, None)
                            self.log_change(request, obj, change_msg)
                            changecount += 1

                    if changecount:
                        if changecount == 1:
                            name = force_unicode(opts.verbose_name)
                        else:
                            name = force_unicode(opts.verbose_name_plural)
                        msg = ungettext("%(count)s %(name)s was changed successfully.",
                                        "%(count)s %(name)s were changed successfully.",
                                        changecount) % {'count': changecount,
                                                        'name': name,
                                                        'obj': force_unicode(obj)}
                        self.message_user(request, msg)

                    return HttpResponseRedirect(request.get_full_path())

            # Handle GET -- construct a formset for display.
            elif self.list_editable:
                FormSet = self.get_changelist_formset(request)
                formset = cl.formset = FormSet(queryset=cl.result_list)

            # Build the list of media to be used by the formset.
            if formset:
                media = self.media + formset.media
            else:
                media = self.media

            # Build the action form and populate it with available actions.
            if actions:
                action_form = self.action_form(auto_id=None)
                action_form.fields['action'].choices = self.get_action_choices(request)
            else:
                action_form = None

            context = {
                'title': cl.title,
                'is_popup': cl.is_popup,
                'cl': cl,
                'media': media,
                'has_add_permission': self.has_add_permission(request),
                'root_path': self.admin_site.root_path,
                'app_label': app_label,
                'action_form': action_form,
                'actions_on_top': self.actions_on_top,
                'actions_on_bottom': self.actions_on_bottom,
            }
            context.update(extra_context or {})
            context_instance = template.RequestContext(request, current_app=self.admin_site.name)
            return render_to_response(self.change_list_template or [
                'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
                'admin/%s/change_list.html' % app_label,
                'admin/change_list.html'
            ], context, context_instance=context_instance)

if getattr(settings, 'MPTT_USE_FEINCMS', True):
    try:
        from feincms.admin import editor
    except ImportError:
        pass
    else:
        __all__ = tuple(list(__all__) + ['FeinCMSModelAdmin'])

        class FeinCMSModelAdmin(editor.TreeEditor):
            """
            A ModelAdmin to add changelist tree view and editing capabilities.
            Requires FeinCMS to be installed.
            """

            form = MPTTAdminForm

            def _actions_column(self, obj):
                actions = super(FeinCMSModelAdmin, self)._actions_column(obj)
                actions.insert(0,
                    u'' % (
                        self.model._mptt_meta.parent_attr,
                        obj.pk,
                        _('Add child'),
                        settings.ADMIN_MEDIA_PREFIX,
                        _('Add child')))

                if hasattr(obj, 'get_absolute_url'):
                    actions.insert(0,
                        u'' % (
                            obj.get_absolute_url(),
                            _('View on site'),
                            settings.ADMIN_MEDIA_PREFIX,
                            _('View on site')))
                return actions

            def delete_selected_tree(self, modeladmin, request, queryset):
                """
                Deletes multiple instances and makes sure the MPTT fields get recalculated properly.
                (Because merely doing a bulk delete doesn't trigger the post_delete hooks.)
                """
                n = 0
                for obj in queryset:
                    obj.delete()
                    n += 1
                self.message_user(request, _("Successfully deleted %s items.") % n)

            def get_actions(self, request):
                actions = super(FeinCMSModelAdmin, self).get_actions(request)
                if 'delete_selected' in actions:
                    actions['delete_selected'] = (self.delete_selected_tree, 'delete_selected', _("Delete selected %(verbose_name_plural)s"))
                return actions
<commit_msg>Fixed admin widget for raw ID fields pointing to MPTTModels.<commit_after>
import django
from django.conf import settings
from django.contrib.admin.views.main import ChangeList
from django.contrib.admin.options import ModelAdmin, IncorrectLookupParameters
from django import template
from django.contrib.admin import helpers
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.utils.encoding import force_unicode

from mptt.forms import MPTTAdminForm, TreeNodeChoiceField

__all__ = ('MPTTChangeList', 'MPTTModelAdmin', 'MPTTAdminForm')

class MPTTChangeList(ChangeList):
    def get_query_set(self, request=None):
        # request arg was added in django r16144 (after 1.3)
        if request is not None and django.VERSION >= (1, 4):
            qs = super(MPTTChangeList, self).get_query_set(request)
        else:
            qs = super(MPTTChangeList, self).get_query_set()

        # always order by (tree_id, left)
        tree_id = qs.model._mptt_meta.tree_id_attr
        left = qs.model._mptt_meta.left_attr
        return qs.order_by(tree_id, left)

class MPTTModelAdmin(ModelAdmin):
    """
    A basic admin class that displays tree items according to their position in the tree.
    No extra editing functionality beyond what Django admin normally offers.
    """

    change_list_template = 'admin/mptt_change_list.html'

    form = MPTTAdminForm

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        from mptt.models import MPTTModel, TreeForeignKey
        if issubclass(db_field.rel.to, MPTTModel) \
                and not isinstance(db_field, TreeForeignKey) \
                and not db_field.name in self.raw_id_fields:
            defaults = dict(form_class=TreeNodeChoiceField, queryset=db_field.rel.to.objects.all(), required=False)
            defaults.update(kwargs)
            kwargs = defaults
        return super(MPTTModelAdmin, self).formfield_for_foreignkey(db_field,
                                                                    request,
                                                                    **kwargs)

    def get_changelist(self, request, **kwargs):
        """
        Returns the ChangeList class for use on the changelist page.
        """
        return MPTTChangeList

    # In Django 1.1, the changelist class is hard coded in changelist_view, so
    # we've got to override this too, just to get it to use our custom ChangeList
    if django.VERSION < (1, 2):
        def changelist_view(self, request, extra_context=None):
            "The 'change list' admin view for this model."
            from django.contrib.admin.views.main import ERROR_FLAG
            opts = self.model._meta
            app_label = opts.app_label
            if not self.has_change_permission(request, None):
                raise PermissionDenied

            # Check actions to see if any are available on this changelist
            actions = self.get_actions(request)

            # Remove action checkboxes if there aren't any actions available.
            list_display = list(self.list_display)
            if not actions:
                try:
                    list_display.remove('action_checkbox')
                except ValueError:
                    pass

            CL = self.get_changelist(request)

            try:
                cl = CL(request, self.model, list_display, self.list_display_links, self.list_filter,
                    self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self)
            except IncorrectLookupParameters:
                # Wacky lookup parameters were given, so redirect to the main
                # changelist page, without parameters, and pass an 'invalid=1'
                # parameter via the query string. If wacky parameters were given and
                # the 'invalid=1' parameter was already in the query string, something
                # is screwed up with the database, so display an error page.
                if ERROR_FLAG in request.GET.keys():
                    return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
                return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')

            # If the request was POSTed, this might be a bulk action or a bulk edit.
            # Try to look up an action or confirmation first, but if this isn't an
            # action the POST will fall through to the bulk edit check, below.
            if actions and request.method == 'POST' and (helpers.ACTION_CHECKBOX_NAME in request.POST or 'index' in request.POST):
                response = self.response_action(request, queryset=cl.get_query_set(request))
                if response:
                    return response

            # If we're allowing changelist editing, we need to construct a formset
            # for the changelist given all the fields to be edited. Then we'll
            # use the formset to validate/process POSTed data.
            formset = cl.formset = None

            # Handle POSTed bulk-edit data.
            if request.method == "POST" and self.list_editable:
                FormSet = self.get_changelist_formset(request)
                formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
                if formset.is_valid():
                    changecount = 0
                    for form in formset.forms:
                        if form.has_changed():
                            obj = self.save_form(request, form, change=True)
                            self.save_model(request, obj, form, change=True)
                            form.save_m2m()
                            change_msg = self.construct_change_message(request, form, None)
                            self.log_change(request, obj, change_msg)
                            changecount += 1

                    if changecount:
                        if changecount == 1:
                            name = force_unicode(opts.verbose_name)
                        else:
                            name = force_unicode(opts.verbose_name_plural)
                        msg = ungettext("%(count)s %(name)s was changed successfully.",
                                        "%(count)s %(name)s were changed successfully.",
                                        changecount) % {'count': changecount,
                                                        'name': name,
                                                        'obj': force_unicode(obj)}
                        self.message_user(request, msg)

                    return HttpResponseRedirect(request.get_full_path())

            # Handle GET -- construct a formset for display.
            elif self.list_editable:
                FormSet = self.get_changelist_formset(request)
                formset = cl.formset = FormSet(queryset=cl.result_list)

            # Build the list of media to be used by the formset.
            if formset:
                media = self.media + formset.media
            else:
                media = self.media

            # Build the action form and populate it with available actions.
            if actions:
                action_form = self.action_form(auto_id=None)
                action_form.fields['action'].choices = self.get_action_choices(request)
            else:
                action_form = None

            context = {
                'title': cl.title,
                'is_popup': cl.is_popup,
                'cl': cl,
                'media': media,
                'has_add_permission': self.has_add_permission(request),
                'root_path': self.admin_site.root_path,
                'app_label': app_label,
                'action_form': action_form,
                'actions_on_top': self.actions_on_top,
                'actions_on_bottom': self.actions_on_bottom,
            }
            context.update(extra_context or {})
            context_instance = template.RequestContext(request, current_app=self.admin_site.name)
            return render_to_response(self.change_list_template or [
                'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
                'admin/%s/change_list.html' % app_label,
                'admin/change_list.html'
            ], context, context_instance=context_instance)

if getattr(settings, 'MPTT_USE_FEINCMS', True):
    try:
        from feincms.admin import editor
    except ImportError:
        pass
    else:
        __all__ = tuple(list(__all__) + ['FeinCMSModelAdmin'])

        class FeinCMSModelAdmin(editor.TreeEditor):
            """
            A ModelAdmin to add changelist tree view and editing capabilities.
            Requires FeinCMS to be installed.
            """

            form = MPTTAdminForm

            def _actions_column(self, obj):
                actions = super(FeinCMSModelAdmin, self)._actions_column(obj)
                actions.insert(0,
                    u'' % (
                        self.model._mptt_meta.parent_attr,
                        obj.pk,
                        _('Add child'),
                        settings.ADMIN_MEDIA_PREFIX,
                        _('Add child')))

                if hasattr(obj, 'get_absolute_url'):
                    actions.insert(0,
                        u'' % (
                            obj.get_absolute_url(),
                            _('View on site'),
                            settings.ADMIN_MEDIA_PREFIX,
                            _('View on site')))
                return actions

            def delete_selected_tree(self, modeladmin, request, queryset):
                """
                Deletes multiple instances and makes sure the MPTT fields get recalculated properly.
                (Because merely doing a bulk delete doesn't trigger the post_delete hooks.)
                """
                n = 0
                for obj in queryset:
                    obj.delete()
                    n += 1
                self.message_user(request, _("Successfully deleted %s items.") % n)

            def get_actions(self, request):
                actions = super(FeinCMSModelAdmin, self).get_actions(request)
                if 'delete_selected' in actions:
                    actions['delete_selected'] = (self.delete_selected_tree, 'delete_selected', _("Delete selected %(verbose_name_plural)s"))
                return actions
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''
Common functions for working with RPM packages
'''

# Import python libs
from __future__ import absolute_import
import collections
import datetime
import logging
import subprocess

# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import range  # pylint: disable=redefined-builtin

log = logging.getLogger(__name__)

# These arches compiled from the rpmUtils.arch python module source
ARCHES_64 = ('x86_64', 'athlon', 'amd64', 'ia32e', 'ia64', 'geode')
ARCHES_32 = ('i386', 'i486', 'i586', 'i686')
ARCHES_PPC = ('ppc', 'ppc64', 'ppc64iseries', 'ppc64pseries')
ARCHES_S390 = ('s390', 's390x')
ARCHES_SPARC = (
    'sparc', 'sparcv8', 'sparcv9', 'sparcv9v', 'sparc64', 'sparc64v'
)
ARCHES_ALPHA = (
    'alpha', 'alphaev4', 'alphaev45', 'alphaev5', 'alphaev56',
    'alphapca56', 'alphaev6', 'alphaev67', 'alphaev68', 'alphaev7'
)
ARCHES_ARM = ('armv5tel', 'armv5tejl', 'armv6l', 'armv7l')
ARCHES_SH = ('sh3', 'sh4', 'sh4a')

ARCHES = ARCHES_64 + ARCHES_32 + ARCHES_PPC + ARCHES_S390 + \
    ARCHES_ALPHA + ARCHES_ARM + ARCHES_SH

# EPOCHNUM can't be used until RHEL5 is EOL as it is not present
QUERYFORMAT = '%{NAME}_|-%{EPOCH}_|-%{VERSION}_|-%{RELEASE}_|-%{ARCH}_|-%{REPOID}_|-%{INSTALLTIME}'

def get_osarch():
    '''
    Get the os architecture using rpm --eval
    '''
    ret = subprocess.Popen(
        'rpm --eval "%{_host_cpu}"',
        shell=True,
        close_fds=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE).communicate()[0]
    return ret or 'unknown'

def check_32(arch, osarch=None):
    '''
    Returns True if both the OS arch and the passed arch are 32-bit
    '''
    if osarch is None:
        osarch = get_osarch()
    return all(x in ARCHES_32 for x in (osarch, arch))

def pkginfo(name, version, arch, repoid, install_date=None, install_date_time_t=None):
    '''
    Build and return a pkginfo namedtuple
    '''
    pkginfo_tuple = collections.namedtuple(
        'PkgInfo',
        ('name', 'version', 'arch', 'repoid', 'install_date',
         'install_date_time_t')
    )
    return pkginfo_tuple(name, version, arch, repoid, install_date,
                         install_date_time_t)

def resolve_name(name, arch, osarch=None):
    '''
    Resolve the package name and arch into a unique name referred to by salt.
    For example, on a 64-bit OS, a 32-bit package will be pkgname.i386.
    '''
    if osarch is None:
        osarch = get_osarch()

    if not check_32(arch, osarch) and arch not in (osarch, 'noarch'):
        name += '.{0}'.format(arch)
    return name

def parse_pkginfo(line, osarch=None):
    '''
    A small helper to parse an rpm/repoquery command's output. Returns a
    pkginfo namedtuple.
    '''
    try:
        name, epoch, version, release, arch, repoid, install_time = line.split('_|-')
    # Handle unpack errors (should never happen with the queryformat we are
    # using, but can't hurt to be careful).
    except ValueError:
        return None

    name = resolve_name(name, arch, osarch)
    if release:
        version += '-{0}'.format(release)
    if epoch not in ('(none)', '0'):
        version = ':'.join((epoch, version))

    if install_time:
        install_date = datetime.datetime.utcfromtimestamp(int(install_time)).isoformat() + "Z"
        install_date_time_t = int(install_time)
    else:
        install_date = None
        install_date_time_t = None

    return pkginfo(name, version, arch, repoid, install_date, install_date_time_t)

def combine_comments(comments):
    '''
    Given a list of comments, strings, a single comment or a single string,
    return a single string of text containing all of the comments, prepending
    the '#' and joining with newlines as necessary.
    '''
    if not isinstance(comments, list):
        comments = [comments]
    for idx in range(len(comments)):
        if not isinstance(comments[idx], six.string_types):
            comments[idx] = str(comments[idx])
        comments[idx] = comments[idx].strip()
        if not comments[idx].startswith('#'):
            comments[idx] = '#' + comments[idx]
    return '\n'.join(comments)

def version_to_evr(verstring):
    '''
    Split the package version string into epoch, version and release.
    Return this as tuple.

    The epoch is always not empty. The version and the release can be an empty
    string if such a component could not be found in the version string.

    "2:1.0-1.2" => ('2', '1.0', '1.2)
    "1.0" => ('0', '1.0', '')
    "" => ('0', '', '')
    '''
    if verstring in [None, '']:
        return '0', '', ''

    idx_e = verstring.find(':')
    if idx_e != -1:
        try:
            epoch = str(int(verstring[:idx_e]))
        except ValueError:
            # look, garbage in the epoch field, how fun, kill it
            epoch = '0'  # this is our fallback, deal
    else:
        epoch = '0'
    idx_r = verstring.find('-')
    if idx_r != -1:
        version = verstring[idx_e + 1:idx_r]
        release = verstring[idx_r + 1:]
    else:
        version = verstring[idx_e + 1:]
        release = ''

    return epoch, version, release
<commit_msg>Checking install_time for '(none)' value<commit_after># -*- coding: utf-8 -*-
'''
Common functions for working with RPM packages
'''

# Import python libs
from __future__ import absolute_import
import collections
import datetime
import logging
import subprocess

# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import range  # pylint: disable=redefined-builtin

log = logging.getLogger(__name__)

# These arches compiled from the rpmUtils.arch python module source
ARCHES_64 = ('x86_64', 'athlon', 'amd64', 'ia32e', 'ia64', 'geode')
ARCHES_32 = ('i386', 'i486', 'i586', 'i686')
ARCHES_PPC = ('ppc', 'ppc64', 'ppc64iseries', 'ppc64pseries')
ARCHES_S390 = ('s390', 's390x')
ARCHES_SPARC = (
    'sparc', 'sparcv8', 'sparcv9', 'sparcv9v', 'sparc64', 'sparc64v'
)
ARCHES_ALPHA = (
    'alpha', 'alphaev4', 'alphaev45', 'alphaev5', 'alphaev56',
    'alphapca56', 'alphaev6', 'alphaev67', 'alphaev68', 'alphaev7'
)
ARCHES_ARM = ('armv5tel', 'armv5tejl', 'armv6l', 'armv7l')
ARCHES_SH = ('sh3', 'sh4', 'sh4a')

ARCHES = ARCHES_64 + ARCHES_32 + ARCHES_PPC + ARCHES_S390 + \
    ARCHES_ALPHA + ARCHES_ARM + ARCHES_SH

# EPOCHNUM can't be used until RHEL5 is EOL as it is not present
QUERYFORMAT = '%{NAME}_|-%{EPOCH}_|-%{VERSION}_|-%{RELEASE}_|-%{ARCH}_|-%{REPOID}_|-%{INSTALLTIME}'

def get_osarch():
    '''
    Get the os architecture using rpm --eval
    '''
    ret = subprocess.Popen(
        'rpm --eval "%{_host_cpu}"',
        shell=True,
        close_fds=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE).communicate()[0]
    return ret or 'unknown'

def check_32(arch, osarch=None):
    '''
    Returns True if both the OS arch and the passed arch are 32-bit
    '''
    if osarch is None:
        osarch = get_osarch()
    return all(x in ARCHES_32 for x in (osarch, arch))

def pkginfo(name, version, arch, repoid, install_date=None, install_date_time_t=None):
    '''
    Build and return a pkginfo namedtuple
    '''
    pkginfo_tuple = collections.namedtuple(
        'PkgInfo',
        ('name', 'version', 'arch', 'repoid', 'install_date',
         'install_date_time_t')
    )
    return pkginfo_tuple(name, version, arch, repoid, install_date,
                         install_date_time_t)

def resolve_name(name, arch, osarch=None):
    '''
    Resolve the package name and arch into a unique name referred to by salt.
    For example, on a 64-bit OS, a 32-bit package will be pkgname.i386.
    '''
    if osarch is None:
        osarch = get_osarch()

    if not check_32(arch, osarch) and arch not in (osarch, 'noarch'):
        name += '.{0}'.format(arch)
    return name

def parse_pkginfo(line, osarch=None):
    '''
    A small helper to parse an rpm/repoquery command's output. Returns a
    pkginfo namedtuple.
    '''
    try:
        name, epoch, version, release, arch, repoid, install_time = line.split('_|-')
    # Handle unpack errors (should never happen with the queryformat we are
    # using, but can't hurt to be careful).
    except ValueError:
        return None

    name = resolve_name(name, arch, osarch)
    if release:
        version += '-{0}'.format(release)
    if epoch not in ('(none)', '0'):
        version = ':'.join((epoch, version))

    if install_time not in ('(none)', '0'):
        install_date = datetime.datetime.utcfromtimestamp(int(install_time)).isoformat() + "Z"
        install_date_time_t = int(install_time)
    else:
        install_date = None
        install_date_time_t = None

    return pkginfo(name, version, arch, repoid, install_date, install_date_time_t)

def combine_comments(comments):
    '''
    Given a list of comments, strings, a single comment or a single string,
    return a single string of text containing all of the comments, prepending
    the '#' and joining with newlines as necessary.
    '''
    if not isinstance(comments, list):
        comments = [comments]
    for idx in range(len(comments)):
        if not isinstance(comments[idx], six.string_types):
            comments[idx] = str(comments[idx])
        comments[idx] = comments[idx].strip()
        if not comments[idx].startswith('#'):
            comments[idx] = '#' + comments[idx]
    return '\n'.join(comments)

def version_to_evr(verstring):
    '''
    Split the package version string into epoch, version and release.
    Return this as tuple.

    The epoch is always not empty. The version and the release can be an empty
    string if such a component could not be found in the version string.

    "2:1.0-1.2" => ('2', '1.0', '1.2)
    "1.0" => ('0', '1.0', '')
    "" => ('0', '', '')
    '''
    if verstring in [None, '']:
        return '0', '', ''

    idx_e = verstring.find(':')
    if idx_e != -1:
        try:
            epoch = str(int(verstring[:idx_e]))
        except ValueError:
            # look, garbage in the epoch field, how fun, kill it
            epoch = '0'  # this is our fallback, deal
    else:
        epoch = '0'
    idx_r = verstring.find('-')
    if idx_r != -1:
        version = verstring[idx_e + 1:idx_r]
        release = verstring[idx_r + 1:]
    else:
        version = verstring[idx_e + 1:]
        release = ''

    return epoch, version, release
<|endoftext|>


<commit_before>################################# LICENSE ##################################
# Copyright (c) 2009, South African Astronomical Observatory (SAAO)        #
# All rights reserved.                                                     #
#                                                                          #
# Redistribution and use in source and binary forms, with or without       #
# modification, are permitted provided that the following conditions       #
# are met:                                                                 #
#                                                                          #
#     * Redistributions of source code must retain the above copyright     #
#       notice, this list of conditions and the following disclaimer.      #
#     * Redistributions in binary form must reproduce the above copyright  #
#       notice, this list of conditions and the following disclaimer       #
#       in the documentation and/or other materials provided with the      #
#       distribution.                                                      #
#     * Neither the name of the South African Astronomical Observatory     #
#       (SAAO) nor the names of its contributors may be used to endorse    #
#       or promote products derived from this software without specific    #
#       prior written permission.                                          #
#                                                                          #
# THIS SOFTWARE IS PROVIDED BY THE SAAO ''AS IS'' AND ANY EXPRESS OR       #
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED           #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE   #
# DISCLAIMED. IN NO EVENT SHALL THE SAAO BE LIABLE FOR ANY                 #
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL       #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS  #
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)    #
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,      #
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN #
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          #
# POSSIBILITY OF SUCH DAMAGE.                                              #
############################################################################

#!/usr/bin/env python

# Author                 Version      Date
# -----------------------------------------------
# Martin Still (SAAO)    1.0          25 Jul 2007
# S M Crawford (SAA0)    1.1          16 Jul 2009
# S M Crawford (SAA0)    1.2          19 Apr 2011

# saltobslog reads the critical header keywords of SALT FITS data and
# collates them into a FITS table. This can be used for the basis
# of an observation log or as a meta-table for pipeline processing.
#
# Updates:
# 16 Jul 2009   Handled a bug on how if data is missing in the headers
# 5 Apr 2011    Updated to handle new header key words and changes in existing
#               keywords. We did not make it backwards compatible
#               Changes made:
#               TELTEMP->TELTEM
#               PAYLTEMP -> PAYLTEM
#               Removed DETSIZE
#               Added EPOCH
#               Removed UTC-OBS--now using TIME-OBS
#               FOCUS->CAMFOCUS
#               INTFR->TELFOCUS
# 19 Apr 2011   Updated to handle the new error handling and so it returns the
#               contents without updating everything in the process
#               Converted it to using dictionary instead of a bunch of lists

from __future__ import with_statement

from pyraf import iraf
import os, glob, time
from astropy.io import fits

import saltsafekey as saltkey
import saltsafeio as saltio
from saltsafelog import logging

from salterror import SaltError, SaltIOError

# -----------------------------------------------------------
# core routine

headerList=['FILENAME', 'PROPID', 'PROPOSER', 'OBJECT', 'RA', 'DEC', 'OBJEPOCH', 'EPOCH', 'EQUINOX', 'DATE-OBS', 'UTC-OBS', 'TIME-OBS', 'EXPTIME', 'OBSMODE', 'DETMODE', 'CCDTYPE', 'DETSIZE', 'NCCDS', 'CCDSUM', 'GAINSET', 'ROSPEED', 'INSTRUME', 'FILTER', 'CAMFOCUS', 'TELHA', 'TELRA', 'TELDEC', 'TELPA', 'TELAZ', 'TELALT', 'TRKX', 'TRKY', 'TRKZ', 'TRKPHI', 'TRKTHETA', 'TRKRHO', 'TELFOCUS', 'COLPHI', 'COLTHETA', 'TELTEM', 'PAYLTEM', 'CCDTEM', 'DEWTEM', 'AMPTEM', 'CENTEM', 'DETSWV']
formatList=['32A', '50A', '20A', '100A', '1A', '12A', 'E', 'E', 'E', '10A', '12A',
            '12A', 'D', '20A', '20A', '8A', '23A', 'I', '5A', '6A', '4A', '8A', '8A',
            'J', '11A', '11A', '12A', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E',
            'E', 'E', 'E', 'E', 'E', 'E', 'D', 'E', 'E', 'D', '16A'  ]
rssheaderList=['DEWPRE', 'POSANG', 'LAMPID', 'CALFILT', 'CALND', 'TELRHO', 'PELLICLE', 'INSTPORT', 'CF-STATE', 'SM-STATE', 'SM-STA', 'SM-STEPS', 'SM-VOLTS', 'SM-STA-S', 'SM-STA-V', 'MASKID', 'MASKTYP', 'WP-STATE', 'HWP-CMD', 'HW-STEPS', 'HWP-STA', 'QWP-CMD', 'QW-STEPS', 'QWP-STA', 'QWP-ANG', 'HWP-ANG', 'SH-STATE', 'FO-STATE', 'FO-POS', 'FO-VOLTS', 'FO-POS-S', 'FO-POS-V', 'GR-STATE', 'GR-STA', 'GR-ANGLE', 'GM-STEPS', 'GM-VOLTS', 'GR-STA-S', 'GR-STA-V', 'GR-STEPS', 'GRATING', 'GRTILT', 'BS-STATE', 'FI-STATE', 'FI-STA', 'FM-STEPS', 'FM-VOLTS', 'FM-STA-S', 'FM-STA-V', 'AR-STATE', 'AR-STA', 'CAMANG', 'AR-STA-S', 'AR-ANGLE', 'COLTEMP', 'CAMTEMP', 'PROC', 'PCS-VER', 'WPPATERN']
rssformatList=['D', 'E', '8A', '8A', 'E', 'E', '8A', '8A', '20A', '20A', '8A', 'J', 'E', 'E', 'E', '16A', '16A', '20A', '16A', 'J', 'E', '16A', 'J', 'E', 'E', 'E', '20A', '20A', 'E', 'E', 'E', 'E', '20A', '10A', 'E', 'J', 'E', 'E', 'E', 'J', '8A', 'E', '24A', '20A', '7A', 'J', 'E', 'E', 'E', '24A', '16A', 'E', 'E', 'E', 'E', 'E', '20A', '4A', '20A']
scamheaderList=['FILPOS']
scamformatList=['I']

debug=True

def saltobslog(images,outfile,clobber=False,logfile='salt.log',verbose=True):
  """Create the observation log from the input files"""

  #start the logging
  with logging(logfile,debug) as log:

       # Check the input images
       infiles = saltio.argunpack ('Input',images)

       #create the header dictionary
       headerDict=obslog(infiles, log)

       #clobber the output if it exists
       if (clobber and os.path.isfile(outfile)):
           saltio.delete(outfile)

       #create the fits file
       struct=createobslogfits(headerDict)

       # close table file
       saltio.writefits(struct, outfile)

       #indicate the log was created
       log.message('\nSALTLOG -- created observation log ' + outfile)

def createobslogfits(headerDict):
   """Create the fits table for the observation log"""
   # define generic columns of output table
   col=[]
   for k, f in zip(headerList, formatList):
       print k,f, headerDict[k]
       col.append(fits.Column(name=k, format=f, array=headerDict[k]))
   for k, f in zip(scamheaderList, scamformatList):
       print k,f, headerDict[k]
       col.append(fits.Column(name=k, format=f, array=headerDict[k]))
   for k, f in zip(rssheaderList, rssformatList):
       print k,f, headerDict[k]
       col.append(fits.Column(name=k, format=f, array=headerDict[k]))

   # construct FITS table from columns
   table = fits.ColDefs(col)

   # write FITS table to output file
   struct = fits.BinTableHDU.from_columns(table)

   # name the table extension
   struct.header['EXTNAME'] = 'OBSLOG'
   struct.header['SAL_TLM'] = time.asctime(time.localtime())
   #saltkey.new('EXTNAME','OBSLOG','extension name', struct)
   #saltkey.put('SAL-TLM',time.asctime(time.localtime()), struct)

   # housekeeping keywords

   return struct

# -----------------------------------------------------------
# read keyword and append to list

def obslog(infiles, log=None):
   """For a set of input files, create a dictionary contain all the header
      information from the files.  Will print things to a saltlog if log is
      not None

      returns Dictionary
   """
   #create the header dictionary
   headerDict={}
   for k in headerList: headerDict[k]=[]
   for k in scamheaderList: headerDict[k]=[]
   for k in rssheaderList: headerDict[k]=[]

   # interate over and open image files
   infiles.sort()
   for infile in infiles:

       #open the file
       struct = saltio.openfits(infile)

       # instrument
       scam = False
       rss = False
       instrume = saltkey.get('INSTRUME', struct[0])
       if (instrume=='RSS'): rss = True
       if (instrume=='SALTICAM'): scam=True

       #add in the image name
       headerDict['FILENAME'].append(os.path.basename(infile))

       # ingest primary keywords from files in the image list
       for k,f in zip(headerList[1:], formatList[1:]):
           default=finddefault(f)
           headerDict[k].append(getkey(struct[0], k, default=default, log=log, warn=True))

       # ingest scam specific primary keywords from files in the image list
       for k,f in zip(scamheaderList[1:], scamformatList[1:]):
           default=finddefault(f)
           headerDict[k].append(getkey(struct[0], k, default=default, log=log, warn=scam))

       # ingest rss specific primary keywords from files in the image list
       for k,f in zip(rssheaderList[1:], rssformatList[1:]):
           default=finddefault(f)
           headerDict[k].append(getkey(struct[0], k, default=default, log=log, warn=rss))

       # close image files
       saltio.closefits(struct)

       if log: log.message('SALTOBSLOG -- read %s' % infile, with_header=False)

   return headerDict

def finddefault(f):
   """return the default value given a format"""
   if f.count('A'):
       default="UNKNOWN"
   else:
       default=-999
   return default

def getkey(struct,keyword,default,warn=True, log=None):
   """Return the keyword value.  Throw a warning if it doesn't work """

   try:
        value = saltkey.get(keyword, struct)
        if isinstance(default, str):  value=value.strip()
   except SaltIOError:
        value = default
        infile=struct._file.name
        message = 'WARNING: cannot find keyword %s in %s' %(keyword, infile)
        if warn and log: log.message(message, with_header=False)
   if (str(value).strip() == ''): value = default
   #if (type(value) != type(default)):
   #     infile=struct._file.name
   #     message='WARNING: Type mismatch for %s for  %s in %s[0]' % (str(value), keyword, infile)
   #     if warn and log: log.message(message, with_header=False)
   #     value=default

   return value

# -----------------------------------------------------------
# main code
if not iraf.deftask('saltobslog'):
   parfile = iraf.osfn("saltred$saltobslog.par")
   t = iraf.IrafTaskFactory(taskname="saltobslog",value=parfile,function=saltobslog, pkgname='saltred')
<commit_msg>added block keywords to obslog<commit_after>################################# LICENSE ##################################
# Copyright (c) 2009, South African Astronomical Observatory (SAAO)        #
# All rights reserved.                                                     #
#                                                                          #
# Redistribution and use in source and binary forms, with or without       #
# modification, are permitted provided that the following conditions       #
# are met:                                                                 #
#                                                                          #
#     * Redistributions of source code must retain the above copyright     #
#       notice, this list of conditions and the following disclaimer.      #
#     * Redistributions in binary form must reproduce the above copyright  #
#       notice, this list of conditions and the following disclaimer       #
#       in the documentation and/or other materials provided with the      #
#       distribution.                                                      #
#     * Neither the name of the South African Astronomical Observatory     #
#       (SAAO) nor the names of its contributors may be used to endorse    #
#       or promote products derived from this software without specific    #
#       prior written permission.                                          #
#                                                                          #
# THIS SOFTWARE IS PROVIDED BY THE SAAO ''AS IS'' AND ANY EXPRESS OR       #
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED           #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE   #
# DISCLAIMED. IN NO EVENT SHALL THE SAAO BE LIABLE FOR ANY                 #
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL       #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS  #
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)    #
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,      #
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN #
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          #
# POSSIBILITY OF SUCH DAMAGE.                                              #
############################################################################

#!/usr/bin/env python

# Author                 Version      Date
# -----------------------------------------------
# Martin Still (SAAO)    1.0          25 Jul 2007
# S M Crawford (SAA0)    1.1          16 Jul 2009
# S M Crawford (SAA0)    1.2          19 Apr 2011

# saltobslog reads the critical header keywords of SALT FITS data and
# collates them into a FITS table. This can be used for the basis
# of an observation log or as a meta-table for pipeline processing.
#
# Updates:
# 16 Jul 2009   Handled a bug on how if data is missing in the headers
# 5 Apr 2011    Updated to handle new header key words and changes in existing
#               keywords. We did not make it backwards compatible
#               Changes made:
#               TELTEMP->TELTEM
#               PAYLTEMP -> PAYLTEM
#               Removed DETSIZE
#               Added EPOCH
#               Removed UTC-OBS--now using TIME-OBS
#               FOCUS->CAMFOCUS
#               INTFR->TELFOCUS
# 19 Apr 2011   Updated to handle the new error handling and so it returns the
#               contents without updating everything in the process
#               Converted it to using dictionary instead of a bunch of lists

from __future__ import with_statement

from pyraf import iraf
import os, glob, time
from astropy.io import fits

import saltsafekey as saltkey
import saltsafeio as saltio
from saltsafelog import logging

from salterror import SaltError, SaltIOError

# -----------------------------------------------------------
# core routine

headerList=['FILENAME', 'PROPID', 'PROPOSER', 'OBJECT', 'RA', 'DEC', 'OBJEPOCH', 'EPOCH', 'EQUINOX', 'DATE-OBS', 'UTC-OBS', 'TIME-OBS', 'EXPTIME', 'OBSMODE', 'DETMODE', 'CCDTYPE', 'DETSIZE', 'NCCDS', 'CCDSUM', 'GAINSET', 'ROSPEED', 'INSTRUME', 'FILTER', 'CAMFOCUS', 'TELHA', 'TELRA', 'TELDEC', 'TELPA', 'TELAZ', 'TELALT', 'TRKX', 'TRKY', 'TRKZ', 'TRKPHI', 'TRKTHETA', 'TRKRHO', 'TELFOCUS', 'COLPHI', 'COLTHETA', 'TELTEM', 'PAYLTEM', 'CCDTEM', 'DEWTEM', 'AMPTEM', 'CENTEM', 'DETSWV', 'BLOCKID', 'BVISITID']
formatList=['32A', '50A', '20A', '100A', '1A', '12A', 'E', 'E', 'E', '10A', '12A',
            '12A', 'D', '20A', '20A', '8A', '23A', 'I', '5A', '6A', '4A', '8A', '8A',
            'J', '11A', '11A', '12A', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E',
            'E', 'E', 'E', 'E', 'E', 'E', 'D', 'E', 'E', 'D', '16A', 'E', 'E' ]
rssheaderList=['DEWPRE', 'POSANG', 'LAMPID', 'CALFILT', 'CALND', 'TELRHO', 'PELLICLE', 'INSTPORT', 'CF-STATE', 'SM-STATE', 'SM-STA', 'SM-STEPS', 'SM-VOLTS', 'SM-STA-S', 'SM-STA-V', 'MASKID', 'MASKTYP', 'WP-STATE', 'HWP-CMD', 'HW-STEPS', 'HWP-STA', 'QWP-CMD', 'QW-STEPS', 'QWP-STA', 'QWP-ANG', 'HWP-ANG', 'SH-STATE', 'FO-STATE', 'FO-POS', 'FO-VOLTS', 'FO-POS-S', 'FO-POS-V', 'GR-STATE', 'GR-STA', 'GR-ANGLE', 'GM-STEPS', 'GM-VOLTS', 'GR-STA-S', 'GR-STA-V', 'GR-STEPS', 'GRATING', 'GRTILT', 'BS-STATE', 'FI-STATE', 'FI-STA', 'FM-STEPS', 'FM-VOLTS', 'FM-STA-S', 'FM-STA-V', 'AR-STATE', 'AR-STA', 'CAMANG', 'AR-STA-S', 'AR-ANGLE', 'COLTEMP', 'CAMTEMP', 'PROC', 'PCS-VER', 'WPPATERN']
rssformatList=['D', 'E', '8A', '8A', 'E', 'E', '8A', '8A', '20A', '20A', '8A', 'J', 'E', 'E', 'E', '16A', '16A', '20A', '16A', 'J', 'E', '16A', 'J', 'E', 'E', 'E', '20A', '20A', 'E', 'E', 'E', 'E', '20A', '10A', 'E', 'J', 'E', 'E', 'E', 'J', '8A', 'E', '24A', '20A', '7A', 'J', 'E', 'E', 'E', '24A', '16A', 'E', 'E', 'E', 'E', 'E', '20A', '4A', '20A']
scamheaderList=['FILPOS']
scamformatList=['I']

debug=True

def saltobslog(images,outfile,clobber=False,logfile='salt.log',verbose=True):
  """Create the observation log from the input files"""

  #start the logging
  with logging(logfile,debug) as log:

       # Check the input images
       infiles = saltio.argunpack ('Input',images)

       #create the header dictionary
       headerDict=obslog(infiles, log)

       #clobber the output if it exists
       if (clobber and os.path.isfile(outfile)):
           saltio.delete(outfile)

       #create the fits file
       struct=createobslogfits(headerDict)

       # close table file
       saltio.writefits(struct, outfile)

       #indicate the log was created
       log.message('\nSALTLOG -- created observation log ' + outfile)

def createobslogfits(headerDict):
   """Create the fits table for the observation log"""
   # define generic columns of output table
   col=[]
   for k, f in zip(headerList, formatList):
       print k,f, headerDict[k]
       col.append(fits.Column(name=k, format=f, array=headerDict[k]))
   for k, f in zip(scamheaderList, scamformatList):
       print k,f, headerDict[k]
       col.append(fits.Column(name=k, format=f, array=headerDict[k]))
   for k, f in zip(rssheaderList, rssformatList):
       print k,f, headerDict[k]
       col.append(fits.Column(name=k, format=f, array=headerDict[k]))

   # construct FITS table from columns
   table = fits.ColDefs(col)

   # write FITS table to output file
   struct = fits.BinTableHDU.from_columns(table)

   # name the table extension
   struct.header['EXTNAME'] = 'OBSLOG'
   struct.header['SAL_TLM'] = time.asctime(time.localtime())
   #saltkey.new('EXTNAME','OBSLOG','extension name', struct)
   #saltkey.put('SAL-TLM',time.asctime(time.localtime()), struct)

   # housekeeping keywords

   return struct

# -----------------------------------------------------------
# read keyword and append to list

def obslog(infiles, log=None):
   """For a set of input files, create a dictionary contain all the header
      information from the files.  Will print things to a saltlog if log is
      not None

      returns Dictionary
   """
   #create the header dictionary
   headerDict={}
   for k in headerList: headerDict[k]=[]
   for k in scamheaderList: headerDict[k]=[]
   for k in rssheaderList: headerDict[k]=[]

   # interate over and open image files
   infiles.sort()
   for infile in infiles:

       #open the file
       struct = saltio.openfits(infile)

       # instrument
       scam = False
       rss = False
       instrume = saltkey.get('INSTRUME', struct[0])
       if (instrume=='RSS'): rss = True
       if (instrume=='SALTICAM'): scam=True

       #add in the image name
       headerDict['FILENAME'].append(os.path.basename(infile))

       # ingest primary keywords from files in the image list
       for k,f in zip(headerList[1:], formatList[1:]):
           default=finddefault(f)
           headerDict[k].append(getkey(struct[0], k, default=default, log=log, warn=True))

       # ingest scam specific primary keywords from files in the image list
       for k,f in zip(scamheaderList[1:], scamformatList[1:]):
           default=finddefault(f)
           headerDict[k].append(getkey(struct[0], k, default=default, log=log, warn=scam))

       # ingest rss specific primary keywords from files in the image list
       for k,f in zip(rssheaderList[1:], rssformatList[1:]):
           default=finddefault(f)
           headerDict[k].append(getkey(struct[0], k, default=default, log=log, warn=rss))

       # close image files
       saltio.closefits(struct)

       if log: log.message('SALTOBSLOG -- read %s' % infile, with_header=False)

   return headerDict

def finddefault(f):
   """return the default value given a format"""
   if f.count('A'):
       default="UNKNOWN"
   else:
       default=-999
   return default

def getkey(struct,keyword,default,warn=True, log=None):
   """Return the keyword value.  Throw a warning if it doesn't work """

   try:
        value = saltkey.get(keyword, struct)
        if isinstance(default, str):  value=value.strip()
   except SaltIOError:
        value = default
        infile=struct._file.name
        message = 'WARNING: cannot find keyword %s in %s' %(keyword, infile)
        if warn and log: log.message(message, with_header=False)
   if (str(value).strip() == ''): value = default
   #if (type(value) != type(default)):
   #     infile=struct._file.name
   #     message='WARNING: Type mismatch for %s for  %s in %s[0]' % (str(value), keyword, infile)
   #     if warn and log: log.message(message, with_header=False)
   #     value=default

   return value

# -----------------------------------------------------------
# main code
if not iraf.deftask('saltobslog'):
   parfile = iraf.osfn("saltred$saltobslog.par")
   t = iraf.IrafTaskFactory(taskname="saltobslog",value=parfile,function=saltobslog, pkgname='saltred')
<|endoftext|>


<commit_before># bleep: BLE Abstraction Library for Python
#
# Copyright (c) 2015 Matthew Else
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#  http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# 2/3 compatibility
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

from future.utils import bytes_to_native_str, native_str_to_bytes
from future.builtins import int, bytes

from uuid import UUID

import json
import os

BASE_UUID = UUID("00000000-0000-1000-8000-00805F9B34FB")

def merge_dicts(*dict_args):
    '''
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    '''
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

class BLEUUID(object):
    BASE_UUID_BYTES = bytearray(BASE_UUID.bytes)

    CHAR_UUIDS = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'chars.json')))
    SERVICE_UUIDS = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'services.json')))
    DESC_UUIDS = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'descriptors.json')))

    UUID_LOOKUP = merge_dicts(CHAR_UUIDS, SERVICE_UUIDS, DESC_UUIDS)

    def __init__(self, uuid):
        self._uuid = BLEUUID.BASE_UUID_BYTES

        if isinstance(uuid, UUID):
            # Assume that the UUID is correct
            self._uuid = bytearray(uuid.bytes)
        elif isinstance(uuid, str):
            if len(uuid) == 4:
                # 16-bit UUID
                part = int(uuid, 16).to_bytes(2, 'little')
                self._uuid[2:4] = bytearray(part)
            elif len(uuid) == 8:
                # 32-bit UUID
                part = int(uuid, 16).to_bytes(4, 'little')
                self._uuid[0:4] = bytearray(part)
            elif len(uuid) == 36:
                # 128-bit UUID
                self._uuid = bytearray(UUID(uuid).bytes)
            else:
                raise ValueError("Invalid UUID")
        elif isinstance(uuid, int):
            if uuid < 65536:
                part = int(uuid).to_bytes(2, 'little')
                self._uuid[2:4] = bytearray(part)
            elif uuid < 2**32:
                part = int(uuid).to_bytes(4, 'little')
                self._uuid[0:4] = bytearray(part)
            else:
                raise ValueError("Invalid UUID")

    def full_uuid(self):
        return str(UUID(bytes=str(self._uuid)))

    def canonical_str(self):
        if self._uuid[4:] == BLEUUID.BASE_UUID_BYTES[4:]:
            # At least a 32-bit UUID
            if self._uuid[:2] == BLEUUID.BASE_UUID_BYTES[:2]:
                # 16-bit UUID
                return self.full_uuid()[4:8]
            else:
                return self.full_uuid()[:8]
        else:
            return self.full_uuid()

    def __str__(self):
        c_str = self.canonical_str()
        return c_str if c_str not in BLEUUID.UUID_LOOKUP else BLEUUID.UUID_LOOKUP[c_str]['name']

    def __repr__(self):
        return "BLEUUID('%s')" % self.full_uuid()

class UUIDAccessor(object):
    def __init__(self, data, all = False):
        self.data = data
        self.all = all

    def __iter__(self):
        for obj in self.data.itervalues():
            for item in obj:
                yield item

    def __getitem__(self, uuid):
        if not isinstance(uuid, BLEUUID):
            uuid = BLEUUID(uuid)

        if self.all:
            return self.data[uuid]

        if len(self.data[uuid]) == 1:
            return self.data[uuid][0]
        else:
            raise KeyError("More than one instance of %s" % uuid)
<commit_msg>Add documentation to BLEUUID<commit_after># bleep: BLE Abstraction Library for Python
#
# Copyright (c) 2015 Matthew Else
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#  http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# 2/3 compatibility
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

from future.utils import bytes_to_native_str, native_str_to_bytes
from future.builtins import int, bytes

from uuid import UUID

import json
import os

BASE_UUID = UUID("00000000-0000-1000-8000-00805F9B34FB")

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in later dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

class BLEUUID(object):
    """Representation of BLE UUIDs, with useful tools"""
    BASE_UUID_BYTES = bytearray(BASE_UUID.bytes)

    CHAR_UUIDS = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'chars.json')))
    SERVICE_UUIDS = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'services.json')))
    DESC_UUIDS = json.load(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'descriptors.json')))

    UUID_LOOKUP = merge_dicts(CHAR_UUIDS, SERVICE_UUIDS, DESC_UUIDS)

    def __init__(self, uuid):
        self._uuid = BLEUUID.BASE_UUID_BYTES

        if isinstance(uuid, UUID):
            # Assume that the UUID is correct
            self._uuid = bytearray(uuid.bytes)
        elif isinstance(uuid, str):
            if len(uuid) == 4:
                # 16-bit UUID
                part = int(uuid, 16).to_bytes(2, 'little')
                self._uuid[2:4] = bytearray(part)
            elif len(uuid) == 8:
                # 32-bit UUID
                part = int(uuid, 16).to_bytes(4, 'little')
                self._uuid[0:4] = bytearray(part)
            elif len(uuid) == 36:
                # 128-bit UUID
                self._uuid = bytearray(UUID(uuid).bytes)
            else:
                raise ValueError("Invalid UUID")
        elif isinstance(uuid, int):
            if uuid < 65536:
                # 16-bit UUID
                part = int(uuid).to_bytes(2, 'little')
                self._uuid[2:4] = bytearray(part)
            elif uuid < 2**32:
                # 32-bit UUID
                part = int(uuid).to_bytes(4, 'little')
                self._uuid[0:4] = bytearray(part)
            else:
                raise ValueError("Invalid UUID")

    def full_uuid_str(self):
        """Return a string representation of the full UUID (128-bit)"""
        return str(UUID(bytes=str(self._uuid)))

    def canonical_str(self):
        """Return the shortest specific string representation of the UUID"""
        if self._uuid[4:] == BLEUUID.BASE_UUID_BYTES[4:]:
            # At least a 32-bit UUID
            if self._uuid[:2] == BLEUUID.BASE_UUID_BYTES[:2]:
                # 16-bit UUID
                return self.full_uuid_str()[4:8]
            else:
                return self.full_uuid_str()[:8]
        else:
            return self.full_uuid_str()

    def __str__(self):
        c_str = self.canonical_str()
        return c_str if c_str not in BLEUUID.UUID_LOOKUP else BLEUUID.UUID_LOOKUP[c_str]['name']

    def __repr__(self):
        return "BLEUUID('%s')" % self.full_uuid_str()

class UUIDAccessor(object):
    def __init__(self, data, all = False):
        self.data = data
        self.all = all

    def __iter__(self):
        for obj in self.data.itervalues():
            for item in obj:
                yield item

    def __getitem__(self, uuid):
        if not isinstance(uuid, BLEUUID):
            uuid = BLEUUID(uuid)

        if self.all:
            return self.data[uuid]

        if len(self.data[uuid]) == 1:
            return self.data[uuid][0]
        else:
            raise KeyError("More than one instance of %s" % uuid)
<|endoftext|>


<commit_before>import json

from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.template.loader import render_to_string

from crits.backdoors.backdoor import Backdoor
from crits.core.class_mapper import class_from_type
from crits.core.crits_mongoengine import EmbeddedCampaign, json_handler
from crits.core.crits_mongoengine import EmbeddedSource
from crits.core.crits_mongoengine import create_embedded_source
from crits.core.handlers import build_jtable, jtable_ajax_list
from crits.core.handlers import jtable_ajax_delete
from crits.core.handlers import csv_export
from crits.core.user_tools import is_admin, is_user_subscribed, user_sources
from crits.core.user_tools import is_user_favorite
from crits.notifications.handlers import remove_user_from_notification
from crits.services.handlers import run_triage, get_supported_services

def generate_backdoor_csv(request):
    """
    Generate a CSV file of the Backdoor information

    :param request: The request for this CSV.
    :type request: :class:`django.http.HttpRequest`
    :returns: :class:`django.http.HttpResponse`
    """

    response = csv_export(request, Backdoor)
    return response

def generate_backdoor_jtable(request, option):
    """
    Generate the jtable data for rendering in the list template.

    :param request: The request for this jtable.
    :type request: :class:`django.http.HttpRequest`
    :param option: Action to take.
    :type option: str of either 'jtlist', 'jtdelete', or 'inline'.
    :returns: :class:`django.http.HttpResponse`
    """

    obj_type = Backdoor
    type_ = "backdoor"
    mapper = obj_type._meta['jtable_opts']
    if option == "jtlist":
        # Sets display url
        details_url = mapper['details_url']
        details_url_key = mapper['details_url_key']
        fields = mapper['fields']
        response = jtable_ajax_list(obj_type,
                                    details_url,
                                    details_url_key,
                                    request,
                                    includes=fields)
        return HttpResponse(json.dumps(response,
                                       default=json_handler),
                            content_type="application/json")
    if option == "jtdelete":
        response = {"Result": "ERROR"}
        if jtable_ajax_delete(obj_type, request):
            response = {"Result": "OK"}
        return HttpResponse(json.dumps(response,
                                       default=json_handler),
                            content_type="application/json")
    jtopts = {
        'title': "Backdoors",
        'default_sort': mapper['default_sort'],
        'listurl': reverse('crits.%ss.views.%ss_listing' %
                           (type_, type_), args=('jtlist',)),
        'deleteurl': reverse('crits.%ss.views.%ss_listing' %
                             (type_, type_), args=('jtdelete',)),
        'searchurl': reverse(mapper['searchurl']),
        'fields': mapper['jtopts_fields'],
        'hidden_fields': mapper['hidden_fields'],
        'linked_fields': mapper['linked_fields'],
        'details_link': mapper['details_link'],
        'no_sort': mapper['no_sort']
    }
    jtable = build_jtable(jtopts, request)
    jtable['toolbar'] = [
        {
            'tooltip': "'Add Backdoor'",
            'text': "'Add Backdoor'",
            'click': "function () {$('#new-backdoor').click()}",
        },
    ]
    if option == "inline":
        return render_to_response("jtable.html",
                                  {'jtable': jtable,
                                   'jtid': '%s_listing' % type_,
                                   'button': '%ss_tab' % type_},
                                  RequestContext(request))
    else:
        return render_to_response("%s_listing.html" % type_,
                                  {'jtable': jtable,
                                   'jtid': '%s_listing' % type_},
                                  RequestContext(request))

def get_backdoor_details(id_, user):
    """
    Generate the data to render the Backdoor details template.

    :param id_: The Backdoor ObjectId to get details for.
    :type id_: str
    :param user: The user requesting this information.
    :type user: str
    :returns: template (str), arguments (dict)
    """

    allowed_sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=allowed_sources).first()
    template = None
    args = {}
    if not backdoor:
        template = "error.html"
        error = ('Either no data exists for this Backdoor or you do not have'
                 ' permission to view it.')
        args = {'error': error}
    else:
        backdoor.sanitize("%s" % user)

        # remove pending notifications for user
        remove_user_from_notification("%s" % user, backdoor.id, 'Backdoor')

        # subscription
        subscription = {
            'type': 'Backdoor',
            'id': backdoor.id,
            'subscribed': is_user_subscribed("%s" % user,
                                             'Backdoor',
                                             backdoor.id),
        }

        #objects
        objects = backdoor.sort_objects()

        #relationships
        relationships = backdoor.sort_relationships("%s" % user, meta=True)

        # relationship
        relationship = {
            'type': 'Backdoor',
            'value': backdoor.id
        }

        #comments
        comments = {'comments': backdoor.get_comments(),
                    'url_key': backdoor.id}

        #screenshots
        screenshots = backdoor.get_screenshots(user)

        # favorites
        favorite = is_user_favorite("%s" % user, 'Backdoor', backdoor.id)

        # services
        service_list = get_supported_services('Backdoor')

        # analysis results
        service_results = backdoor.get_analysis_results()

        args = {'objects': objects,
                'relationships': relationships,
                'relationship': relationship,
                'subscription': subscription,
                'favorite': favorite,
                'service_list': service_list,
                'service_results': service_results,
                'screenshots': screenshots,
                'backdoor': backdoor,
                'backdoor_id': id_,
                'comments': comments}
    return template, args

def add_new_backdoor(name, version=None, aliases=None, description=None,
                     source=None, source_method=None, source_reference=None,
                     campaign=None, confidence=None, user=None,
                     bucket_list=None, ticket=None):
    """
    Add an Backdoor to CRITs.

    :param name: The name of the backdoor.
    :type name: str
    :param version: Version of the backdoor.
    :type version: str
    :param aliases: Aliases for the backdoor.
    :type aliases: list or str
    :param description: Description of the backdoor.
    :type description: str
    :param source: Name of the source which provided this information.
    :type source: str
    :param source_method: Method of acquiring this data.
    :type source_method: str
    :param source_reference: A reference to this data.
    :type source_reference: str
    :param campaign: A campaign to attribute to this backdoor.
    :type campaign: str
    :param confidence: Confidence level in the campaign attribution.
    :type confidence: str ("low", "medium", "high")
    :param user: The user adding this backdoor.
    :type user: str
    :param bucket_list: Buckets to assign to this backdoor.
    :type bucket_list: str
    :param ticket: Ticket to assign to this backdoor.
    :type ticket: str
    :returns: dict with keys:
              "success" (boolean),
              "message" (str),
              "id" (str),
              "object" (if successful) :class:`crits.backdoors.backdoor.Backdoor`
    """

    retVal = {'success': False, 'message': ''}

    if isinstance(source, basestring):
        source = [create_embedded_source(source,
                                         reference=source_reference,
                                         method=source_method,
                                         analyst=user)]
    elif isinstance(source, EmbeddedSource):
        source = [source]

    if not source:
        retVal['message'] = "Missing source information."
        return retval

    # When creating a backdoor object we can potentially create multiple
    # objects. If we are given a name but no version we will create an object
    # with just the name (called the "family backdoor"). If given a name and a
    # version we will create the family backdoor and the specific backdoor for
    # that given version.

    # In case we create more than one backdoor object, store the created ones
    # in this list. The list is walked later on and attributes applied to each
    # object.
    objs = []

    # First check if we have the family (name and no version).
    family = Backdoor.objects(name=name, version='').first()

    if not family:
        # Family does not exist, new object. Details are handled later.
        family = Backdoor()
        family.name = name
        family.version = ''

    objs.append(family)

    # Now check if we have the specific instance for this name + version.
    backdoor = None
    if version:
        backdoor = Backdoor.objects(name=name, version=version).first()
        if not backdoor:
            # Backdoor does not exist, new object. Details are handled later.
            backdoor = Backdoor()
            backdoor.name = name
            backdoor.version = version
        objs.append(backdoor)

    # At this point we have a family object and potentially a specific object.
    # Add the common parameters to all objects in the list and save them.
    for backdoor in objs:
        for s in source:
            backdoor.add_source(s)

        # Don't overwrite existing description.
        if description and backdoor.description == '':
            backdoor.description = description.strip()

        if isinstance(campaign, basestring):
            c = EmbeddedCampaign(name=campaign,
                                 confidence=confidence,
                                 analyst=user)
            campaign = [c]

        if campaign:
            for camp in campaign:
                backdoor.add_campaign(camp)

        if aliases:
            if not isinstance(aliases, list):
                aliases = aliases.split(',')
                for alias in aliases:
                    alias = alias.strip()
                    if alias not in backdoor.aliases:
                        backdoor.aliases.append(alias)

        if bucket_list:
            backdoor.add_bucket_list(bucket_list, user)

        if ticket:
            backdoor.add_ticket(ticket, user)

        backdoor.save(username=user)

        # run backdoor triage
        backdoor.reload()
        run_triage(backdoor, user)

        # Because family objects are put in the list first we will always
        # return a link to the most specific object created. If there is only
        # one item in the list it will be the family object.
        resp_url = reverse('crits.backdoors.views.backdoor_detail',
                           args=[backdoor.id])
        retVal['message'] = 'Success: %s' % (resp_url,
                                                              backdoor.name)
        retVal['object'] = backdoor
        retVal['id'] = str(backdoor.id)

    # If we have a family and specific object, attempt to relate the two.
    if len(objs) == 2:
        objs[0].add_relationship(rel_item=objs[1], rel_type='Related_To')
        objs[0].save()
        objs[1].save()

    retVal['success'] = True
    return retVal

def backdoor_remove(id_, username):
    """
    Remove a Backdoor from CRITs.

    :param id_: The ObjectId of the Backdoor to remove.
    :type id_: str
    :param username: The user removing this Backdoor.
    :type username: str
    :returns: dict with keys "success" (boolean) and "message" (str) if failed.
    """

    if is_admin(username):
        backdoor = Backdoor.objects(id=id_).first()
        if backdoor:
            backdoor.delete(username=username)
            return {'success': True}
        else:
            return {'success': False, 'message': 'Could not find Backdoor.'}
    else:
        return {'success': False, 'message': 'Must be an admin to remove'}

def set_backdoor_name(id_, name, user, **kwargs):
    """
    Set a Backdoor name.

    :param id_: Backdoor ObjectId.
    :type id_: str
    :param name: The new name.
    :type name: str
    :param user: The user updating the name.
    :type user: str
    :returns: dict with keys:
              "success" (boolean),
              "message" (str),
    """

    sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=sources).first()
    if not backdoor:
        return {'success': False, 'message': "Could not find backdoor"}

    # Make sure we don't have a backdoor with that name and new version yet.
    existing_backdoor = Backdoor.objects(name=name,
                                         version=backdoor.version,
                                         source__name__in=sources).first()
    if existing_backdoor:
        # Be sure to return the same error message so backdoors can not be
        # enumerated via this method.
        return {'success': False, 'message': "Could not find backdoor"}

    backdoor.name = name.strip()
    backdoor.save(username=user)
    return {'success': True}

def set_backdoor_version(id_, version, user, **kwargs):
    """
    Set a Backdoor name.

    :param id_: Backdoor ObjectId.
    :type id_: str
    :param version: The new version.
    :type version: str
    :param user: The user updating the version.
    :type user: str
    :returns: dict with keys:
              "success" (boolean),
              "message" (str),
    """

    sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=sources).first()
    if not backdoor:
        return {'success': False, 'message': "Could not find backdoor"}

    # Make sure we don't have a backdoor with that name and new version yet.
    existing_backdoor = Backdoor.objects(name=backdoor.name,
                                         version=version,
                                         source__name__in=sources).first()
    if existing_backdoor:
        # Be sure to return the same error message so backdoors can not be
        # enumerated via this method.
        return {'success': False, 'message': "Could not find backdoor"}

    backdoor.version = version.strip()
    backdoor.save(username=user)
    return {'success': True}

def update_backdoor_aliases(id_, aliases, user, **kwargs):
    """
    Update aliases for a Backdoor.

    :param id_: The ObjectId of the Backdoor to update.
    :type id_: str
    :param aliases: The aliases we are setting.
    :type aliases: list
    :param user: The user updating the aliases.
    :type user: str
    :returns: dict
    """

    sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=sources).first()
    if not backdoor:
        return {'success': False,
                'message': 'No backdoor could be found.'}
    else:
        backdoor.update_aliases(aliases)
        backdoor.save(username=user)
        return {'success': True}

def get_backdoor_names(user, **kwargs):
    """
    Get a list of unique backdoor names.

    :param user: The user requesting the names.
    :returns: list of tuples
    """

    sources = user_sources(user)
    only = ('name', 'version')
    bds = Backdoor.objects(source__name__in=sources).only(*only)

    names = []
    for b in bds:
        if (b.name, b.version) not in names:
            names.append((b.name, b.version))
    return names

<commit_msg>Fix typo.<commit_after>import json

from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.template.loader import render_to_string

from crits.backdoors.backdoor import Backdoor
from crits.core.class_mapper import class_from_type
from crits.core.crits_mongoengine import EmbeddedCampaign, json_handler
from crits.core.crits_mongoengine import EmbeddedSource
from crits.core.crits_mongoengine import create_embedded_source
from crits.core.handlers import build_jtable, jtable_ajax_list
from crits.core.handlers import jtable_ajax_delete
from crits.core.handlers import csv_export
from crits.core.user_tools import is_admin, is_user_subscribed, user_sources
from crits.core.user_tools import is_user_favorite
from crits.notifications.handlers import remove_user_from_notification
from crits.services.handlers import run_triage, get_supported_services

def generate_backdoor_csv(request):
    """
    Generate a CSV file of the Backdoor information

    :param request: The request for this CSV.
    :type request: :class:`django.http.HttpRequest`
    :returns: :class:`django.http.HttpResponse`
    """

    response = csv_export(request, Backdoor)
    return response

def generate_backdoor_jtable(request, option):
    """
    Generate the jtable data for rendering in the list template.

    :param request: The request for this jtable.
    :type request: :class:`django.http.HttpRequest`
    :param option: Action to take.
    :type option: str of either 'jtlist', 'jtdelete', or 'inline'.
    :returns: :class:`django.http.HttpResponse`
    """

    obj_type = Backdoor
    type_ = "backdoor"
    mapper = obj_type._meta['jtable_opts']
    if option == "jtlist":
        # Sets display url
        details_url = mapper['details_url']
        details_url_key = mapper['details_url_key']
        fields = mapper['fields']
        response = jtable_ajax_list(obj_type,
                                    details_url,
                                    details_url_key,
                                    request,
                                    includes=fields)
        return HttpResponse(json.dumps(response,
                                       default=json_handler),
                            content_type="application/json")
    if option == "jtdelete":
        response = {"Result": "ERROR"}
        if jtable_ajax_delete(obj_type, request):
            response = {"Result": "OK"}
        return HttpResponse(json.dumps(response,
                                       default=json_handler),
                            content_type="application/json")
    jtopts = {
        'title': "Backdoors",
        'default_sort': mapper['default_sort'],
        'listurl': reverse('crits.%ss.views.%ss_listing' %
                           (type_, type_), args=('jtlist',)),
        'deleteurl': reverse('crits.%ss.views.%ss_listing' %
                             (type_, type_), args=('jtdelete',)),
        'searchurl': reverse(mapper['searchurl']),
        'fields': mapper['jtopts_fields'],
        'hidden_fields': mapper['hidden_fields'],
        'linked_fields': mapper['linked_fields'],
        'details_link': mapper['details_link'],
        'no_sort': mapper['no_sort']
    }
    jtable = build_jtable(jtopts, request)
    jtable['toolbar'] = [
        {
            'tooltip': "'Add Backdoor'",
            'text': "'Add Backdoor'",
            'click': "function () {$('#new-backdoor').click()}",
        },
    ]
    if option == "inline":
        return render_to_response("jtable.html",
                                  {'jtable': jtable,
                                   'jtid': '%s_listing' % type_,
                                   'button': '%ss_tab' % type_},
                                  RequestContext(request))
    else:
        return render_to_response("%s_listing.html" % type_,
                                  {'jtable': jtable,
                                   'jtid': '%s_listing' % type_},
                                  RequestContext(request))

def get_backdoor_details(id_, user):
    """
    Generate the data to render the Backdoor details template.

    :param id_: The Backdoor ObjectId to get details for.
    :type id_: str
    :param user: The user requesting this information.
    :type user: str
    :returns: template (str), arguments (dict)
    """

    allowed_sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=allowed_sources).first()
    template = None
    args = {}
    if not backdoor:
        template = "error.html"
        error = ('Either no data exists for this Backdoor or you do not have'
                 ' permission to view it.')
        args = {'error': error}
    else:
        backdoor.sanitize("%s" % user)

        # remove pending notifications for user
        remove_user_from_notification("%s" % user, backdoor.id, 'Backdoor')

        # subscription
        subscription = {
            'type': 'Backdoor',
            'id': backdoor.id,
            'subscribed': is_user_subscribed("%s" % user,
                                             'Backdoor',
                                             backdoor.id),
        }

        #objects
        objects = backdoor.sort_objects()

        #relationships
        relationships = backdoor.sort_relationships("%s" % user, meta=True)

        # relationship
        relationship = {
            'type': 'Backdoor',
            'value': backdoor.id
        }

        #comments
        comments = {'comments': backdoor.get_comments(),
                    'url_key': backdoor.id}

        #screenshots
        screenshots = backdoor.get_screenshots(user)

        # favorites
        favorite = is_user_favorite("%s" % user, 'Backdoor', backdoor.id)

        # services
        service_list = get_supported_services('Backdoor')

        # analysis results
        service_results = backdoor.get_analysis_results()

        args = {'objects': objects,
                'relationships': relationships,
                'relationship': relationship,
                'subscription': subscription,
                'favorite': favorite,
                'service_list': service_list,
                'service_results': service_results,
                'screenshots': screenshots,
                'backdoor': backdoor,
                'backdoor_id': id_,
                'comments': comments}
    return template, args

def add_new_backdoor(name, version=None, aliases=None, description=None,
                     source=None, source_method=None, source_reference=None,
                     campaign=None, confidence=None, user=None,
                     bucket_list=None, ticket=None):
    """
    Add an Backdoor to CRITs.

    :param name: The name of the backdoor.
    :type name: str
    :param version: Version of the backdoor.
    :type version: str
    :param aliases: Aliases for the backdoor.
    :type aliases: list or str
    :param description: Description of the backdoor.
    :type description: str
    :param source: Name of the source which provided this information.
    :type source: str
    :param source_method: Method of acquiring this data.
    :type source_method: str
    :param source_reference: A reference to this data.
    :type source_reference: str
    :param campaign: A campaign to attribute to this backdoor.
    :type campaign: str
    :param confidence: Confidence level in the campaign attribution.
    :type confidence: str ("low", "medium", "high")
    :param user: The user adding this backdoor.
    :type user: str
    :param bucket_list: Buckets to assign to this backdoor.
    :type bucket_list: str
    :param ticket: Ticket to assign to this backdoor.
    :type ticket: str
    :returns: dict with keys:
              "success" (boolean),
              "message" (str),
              "id" (str),
              "object" (if successful) :class:`crits.backdoors.backdoor.Backdoor`
    """

    retVal = {'success': False, 'message': ''}

    if isinstance(source, basestring):
        source = [create_embedded_source(source,
                                         reference=source_reference,
                                         method=source_method,
                                         analyst=user)]
    elif isinstance(source, EmbeddedSource):
        source = [source]

    if not source:
        retVal['message'] = "Missing source information."
        return retVal

    # When creating a backdoor object we can potentially create multiple
    # objects. If we are given a name but no version we will create an object
    # with just the name (called the "family backdoor"). If given a name and a
    # version we will create the family backdoor and the specific backdoor for
    # that given version.

    # In case we create more than one backdoor object, store the created ones
    # in this list. The list is walked later on and attributes applied to each
    # object.
    objs = []

    # First check if we have the family (name and no version).
    family = Backdoor.objects(name=name, version='').first()

    if not family:
        # Family does not exist, new object. Details are handled later.
        family = Backdoor()
        family.name = name
        family.version = ''

    objs.append(family)

    # Now check if we have the specific instance for this name + version.
    backdoor = None
    if version:
        backdoor = Backdoor.objects(name=name, version=version).first()
        if not backdoor:
            # Backdoor does not exist, new object. Details are handled later.
            backdoor = Backdoor()
            backdoor.name = name
            backdoor.version = version
        objs.append(backdoor)

    # At this point we have a family object and potentially a specific object.
    # Add the common parameters to all objects in the list and save them.
    for backdoor in objs:
        for s in source:
            backdoor.add_source(s)

        # Don't overwrite existing description.
        if description and backdoor.description == '':
            backdoor.description = description.strip()

        if isinstance(campaign, basestring):
            c = EmbeddedCampaign(name=campaign,
                                 confidence=confidence,
                                 analyst=user)
            campaign = [c]

        if campaign:
            for camp in campaign:
                backdoor.add_campaign(camp)

        if aliases:
            if not isinstance(aliases, list):
                aliases = aliases.split(',')
                for alias in aliases:
                    alias = alias.strip()
                    if alias not in backdoor.aliases:
                        backdoor.aliases.append(alias)

        if bucket_list:
            backdoor.add_bucket_list(bucket_list, user)

        if ticket:
            backdoor.add_ticket(ticket, user)

        backdoor.save(username=user)

        # run backdoor triage
        backdoor.reload()
        run_triage(backdoor, user)

        # Because family objects are put in the list first we will always
        # return a link to the most specific object created. If there is only
        # one item in the list it will be the family object.
        resp_url = reverse('crits.backdoors.views.backdoor_detail',
                           args=[backdoor.id])
        retVal['message'] = 'Success: %s' % (resp_url,
                                                              backdoor.name)
        retVal['object'] = backdoor
        retVal['id'] = str(backdoor.id)

    # If we have a family and specific object, attempt to relate the two.
    if len(objs) == 2:
        objs[0].add_relationship(rel_item=objs[1], rel_type='Related_To')
        objs[0].save()
        objs[1].save()

    retVal['success'] = True
    return retVal

def backdoor_remove(id_, username):
    """
    Remove a Backdoor from CRITs.

    :param id_: The ObjectId of the Backdoor to remove.
    :type id_: str
    :param username: The user removing this Backdoor.
    :type username: str
    :returns: dict with keys "success" (boolean) and "message" (str) if failed.
    """

    if is_admin(username):
        backdoor = Backdoor.objects(id=id_).first()
        if backdoor:
            backdoor.delete(username=username)
            return {'success': True}
        else:
            return {'success': False, 'message': 'Could not find Backdoor.'}
    else:
        return {'success': False, 'message': 'Must be an admin to remove'}

def set_backdoor_name(id_, name, user, **kwargs):
    """
    Set a Backdoor name.

    :param id_: Backdoor ObjectId.
    :type id_: str
    :param name: The new name.
    :type name: str
    :param user: The user updating the name.
    :type user: str
    :returns: dict with keys:
              "success" (boolean),
              "message" (str),
    """

    sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=sources).first()
    if not backdoor:
        return {'success': False, 'message': "Could not find backdoor"}

    # Make sure we don't have a backdoor with that name and new version yet.
    existing_backdoor = Backdoor.objects(name=name,
                                         version=backdoor.version,
                                         source__name__in=sources).first()
    if existing_backdoor:
        # Be sure to return the same error message so backdoors can not be
        # enumerated via this method.
        return {'success': False, 'message': "Could not find backdoor"}

    backdoor.name = name.strip()
    backdoor.save(username=user)
    return {'success': True}

def set_backdoor_version(id_, version, user, **kwargs):
    """
    Set a Backdoor name.

    :param id_: Backdoor ObjectId.
    :type id_: str
    :param version: The new version.
    :type version: str
    :param user: The user updating the version.
    :type user: str
    :returns: dict with keys:
              "success" (boolean),
              "message" (str),
    """

    sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=sources).first()
    if not backdoor:
        return {'success': False, 'message': "Could not find backdoor"}

    # Make sure we don't have a backdoor with that name and new version yet.
    existing_backdoor = Backdoor.objects(name=backdoor.name,
                                         version=version,
                                         source__name__in=sources).first()
    if existing_backdoor:
        # Be sure to return the same error message so backdoors can not be
        # enumerated via this method.
        return {'success': False, 'message': "Could not find backdoor"}

    backdoor.version = version.strip()
    backdoor.save(username=user)
    return {'success': True}

def update_backdoor_aliases(id_, aliases, user, **kwargs):
    """
    Update aliases for a Backdoor.

    :param id_: The ObjectId of the Backdoor to update.
    :type id_: str
    :param aliases: The aliases we are setting.
    :type aliases: list
    :param user: The user updating the aliases.
    :type user: str
    :returns: dict
    """

    sources = user_sources(user)
    backdoor = Backdoor.objects(id=id_, source__name__in=sources).first()
    if not backdoor:
        return {'success': False,
                'message': 'No backdoor could be found.'}
    else:
        backdoor.update_aliases(aliases)
        backdoor.save(username=user)
        return {'success': True}

def get_backdoor_names(user, **kwargs):
    """
    Get a list of unique backdoor names.

    :param user: The user requesting the names.
    :returns: list of tuples
    """

    sources = user_sources(user)
    only = ('name', 'version')
    bds = Backdoor.objects(source__name__in=sources).only(*only)

    names = []
    for b in bds:
        if (b.name, b.version) not in names:
            names.append((b.name, b.version))
    return names

<|endoftext|>