<commit_before>from django.conf import settings as django_settings
from django.contrib import admin
from django.db.models.query import QuerySet
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, HttpResponseServerError
from django.utils import simplejson
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _

from feincms import settings

import logging

# ------------------------------------------------------------------------
def django_boolean_icon(field_val, alt_text=None, title=None):
    """
    Return HTML code for a nice representation of true/false.
    """

    # Origin: contrib/admin/templatetags/admin_list.py
    BOOLEAN_MAPPING = { True: 'yes', False: 'no', None: 'unknown' }
    alt_text = alt_text or BOOLEAN_MAPPING[field_val]
    if title is not None:
        title = 'title="%s" ' % title
    else:
        title = ''
    return mark_safe(u'' %
            (django_settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], alt_text, title))

def _build_tree_structure(cls):
    """
    Build an in-memory representation of the item tree, trying to keep
    database accesses down to a minimum. The returned dictionary looks like
    this (as json dump):

        {"6": {"id": 6, "children": [7, 8, 10], "parent": null, "descendants": [7, 12, 13, 8, 10]},
         "7": {"id": 7, "children": [12], "parent": 6, "descendants": [12, 13]},
         "8": {"id": 8, "children": [], "parent": 6, "descendants": []},
         ...

    """
    all_nodes = { }
    def add_as_descendant(n, p):
        all_nodes[n]['descendants'].append(p)

        n_parent_id = all_nodes[n]['parent']

        if n_parent_id:
            add_as_descendant(n_parent_id, p)

    for p_id, parent_id in cls.objects.order_by(cls._meta.tree_id_attr, cls._meta.left_attr).values_list("pk", "%s_id" % cls._meta.parent_attr):

        all_nodes[p_id] = { 'id': p_id, 'children' : [ ], 'descendants' : [ ], 'parent' : parent_id }

        if parent_id:
            all_nodes[parent_id]['children'].append(p_id)
            add_as_descendant(parent_id, p_id)

    return all_nodes

# ------------------------------------------------------------------------
def ajax_editable_boolean_cell(item, attr, text='', override=None):
    """
    Generate a html snippet for showing a boolean value on the admin page.
    Item is an object, attr is the attribute name we should display. Text
    is an optional explanatory text to be included in the output.

    This function will emit code to produce a checkbox input with its state
    corresponding to the item.attr attribute if no override value is passed.
    This input is wired to run a JS ajax updater to toggle the value.

    If override is passed in, ignores the attr attribute and returns a
    static image for the override boolean with no user interaction possible
    (useful for "disabled and you can't change it" situations).
    """
    if text:
        text = ' (%s)' % unicode(text)

    if override is not None:
        a = [ django_boolean_icon(override, text), text ]
    else:
        value = getattr(item, attr)
        a = [
              '<input type="checkbox"',
              value and ' checked="checked"' or '',
              ' onclick="return inplace_toggle_boolean(%d, \'%s\')";' % (item.id, attr),
              ' />',
              text,
            ]

    a.insert(0, '
' % ( attr, item.id ))
    a.append('
')
    #print a
    return unicode(''.join(a))

# ------------------------------------------------------------------------
def ajax_editable_boolean(attr, short_description):
    """
    Convenience function: Assign the return value of this method to a variable
    of your ModelAdmin class and put the variable name into list_display.

    Example:
        class MyTreeEditor(TreeEditor):
            list_display = ('__unicode__', 'active_toggle')

            active_toggle = ajax_editable_boolean('active', _('is active'))
    """
    def _fn(self, item):
        return ajax_editable_boolean_cell(item, attr)
    _fn.allow_tags = True
    _fn.short_description = short_description
    _fn.editable_boolean_field = attr
    return _fn

# ------------------------------------------------------------------------
class TreeEditorQuerySet(QuerySet):
    """
    The TreeEditorQuerySet is a special query set used only in the TreeEditor
    ChangeList page. The only difference to a regular QuerySet is that it
    will enforce:

        (a) The result is ordered in correct tree order so that
            the TreeAdmin works all right.

        (b) It ensures that all ancestors of selected items are included
            in the result set, so the resulting tree display actually
            makes sense.
    """
    def iterator(self):
        qs = self
        # Reaching into the bowels of query sets to find out whether the qs is
        # actually filtered and we need to do the INCLUDE_ANCESTORS dance at all.
        # INCLUDE_ANCESTORS is quite expensive, so don't do it if not needed.
        is_filtered = bool(qs.query.where.children)
        if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS and is_filtered:
            include_pages = set()
            # Order by 'rght' will return the tree deepest nodes first;
            # this cuts down the number of queries considerably since all ancestors
            # will already be in include_pages when they are checked, thus not
            # trigger additional queries.
            for p in super(TreeEditorQuerySet, self.order_by('rght')).iterator():
                if p.parent_id and p.parent_id not in include_pages and \
                                   p.id not in include_pages:
                    ancestor_id_list = p.get_ancestors().values_list('id', flat=True)
                    include_pages.update(ancestor_id_list)

            if include_pages:
                qs = qs | self.model._default_manager.filter(id__in=include_pages)

            qs = qs.distinct()

        for obj in super(TreeEditorQuerySet, qs).iterator():
            yield obj

    if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS:
        def __getitem__(self, index):
            return self   # Don't even try to slice

        def get(self, *args, **kwargs):
            """
            Quick and dirty hack to fix change_view and delete_view; they use
            self.queryset(request).get(...) to get the object they should work
            with. Our modifications to the queryset when INCLUDE_ANCESTORS is
            enabled make get() fail often with a MultipleObjectsReturned
            exception.
            """
            return self.model._default_manager.get(*args, **kwargs)

# !!!: Hack alert! Patching ChangeList, check whether this still applies post Django 1.1
# If the ChangeList is used by a TreEditor, we always need to order by 'tree_id' and 'lft'.
from django.contrib.admin.views import main
class ChangeList(main.ChangeList):
    def get_query_set(self):
        qs = super(ChangeList, self).get_query_set()
        if isinstance(self.model_admin, TreeEditor):
            return qs.order_by('tree_id', 'lft')
        return qs
main.ChangeList = ChangeList

# ------------------------------------------------------------------------
# MARK: -
# ------------------------------------------------------------------------

class TreeEditor(admin.ModelAdmin):
    if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS:
        # Make sure that no pagination is displayed. Slicing is disabled anyway,
        # therefore this value does not have an influence on the queryset
        list_per_page = 999999999

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

        self.list_display = list(self.list_display)

        if 'indented_short_title' not in self.list_display:
            if self.list_display[0] == 'action_checkbox':
                self.list_display[1] = 'indented_short_title'
            else:
                self.list_display[0] = 'indented_short_title'
        self.list_display_links = ('indented_short_title',)

        opts = self.model._meta
        self.change_list_template = [
            'admin/feincms/%s/%s/tree_editor.html' % (opts.app_label, opts.object_name.lower()),
            'admin/feincms/%s/tree_editor.html' % opts.app_label,
            'admin/feincms/tree_editor.html',
            ]

    def indented_short_title(self, item):
        """
        Generate a short title for a page, indent it depending on
        the page's depth in the hierarchy.
        """
        r = '''  ''' % (
                item.id, item.id, 14+item.level*18)

#        r += ''
        if hasattr(item, 'short_title'):
            r += item.short_title()
        else:
            r += unicode(item)
#        r += ''
        return mark_safe(r)
    indented_short_title.short_description = _('title')
    indented_short_title.allow_tags = True

    def _collect_editable_booleans(self):
        """
        Collect all fields marked as editable booleans. We do not
        want the user to be able to edit arbitrary fields by crafting
        an AJAX request by hand.
        """
        if hasattr(self, '_ajax_editable_booleans'):
            return

        self._ajax_editable_booleans = {}

        for field in self.list_display:
            # The ajax_editable_boolean return value has to be assigned
            # to the ModelAdmin class
            item = getattr(self.__class__, field, None)
            if not item:
                continue

            attr = getattr(item, 'editable_boolean_field', None)
            if attr:
                def _fn(self, page):
                    return [ ajax_editable_boolean_cell(page, _fn.attr) ]
                _fn.attr = attr
                result_func = getattr(item, 'editable_boolean_result', _fn)
                self._ajax_editable_booleans[attr] = result_func

    def _refresh_changelist_caches(self):
        """
        Refresh information used to show the changelist tree structure such as
        inherited active/inactive states etc.

        XXX: This is somewhat hacky, but since it's an internal method, so be it.
        """

        pass

    def _toggle_boolean(self, request):
        """
        Handle an AJAX toggle_boolean request
        """
        try:
            item_id = int(request.POST.get('item_id', None))
            attr = str(request.POST.get('attr', None))
        except:
            return HttpResponseBadRequest("Malformed request")

        if not request.user.is_staff:
            logging.warning("Denied AJAX request by non-staff %s to toggle boolean %s for page #%s", request.user, attr, item_id)
            return HttpResponseForbidden("You do not have permission to access this page")

        self._collect_editable_booleans()

        if not self._ajax_editable_booleans.has_key(attr):
            return HttpResponseBadRequest("not a valid attribute %s" % attr)

        try:
            obj = self.model._default_manager.get(pk=item_id)
        except self.model.DoesNotExist:
            return HttpResponseNotFound("Object does not exist")

        can_change = False

        if hasattr(obj, "user_can") and obj.user_can(request.user, change_page=True):
            can_change = True
        else:
            can_change = request.user.has_perm("page.change_page")

        if not can_change:
            logging.warning("Denied AJAX request by %s to toggle boolean %s for page %s", request.user, attr, item_id)
            return HttpResponseForbidden("You do not have permission to access this page")

        logging.info("Processing request by %s to toggle %s on %s", request.user, attr, obj)

        try:
            before_data = self._ajax_editable_booleans[attr](self, obj)

            setattr(obj, attr, not getattr(obj, attr))
            obj.save()

            self._refresh_changelist_caches() # ???: Perhaps better a post_save signal?

            # Construct html snippets to send back to client for status update
            data = self._ajax_editable_booleans[attr](self, obj)

        except Exception, e:
            logging.exception("Unhandled exception while toggling %s on %s", attr, obj)
            return HttpResponseServerError("Unable to toggle %s on %s" % (attr, obj))

        # Weed out unchanged cells to keep the updates small. This assumes
        # that the order a possible get_descendents() returns does not change
        # before and after toggling this attribute. Unlikely, but still...
        d = []
        for a, b in zip(before_data, data):
            if a != b:
                d.append(b)

        return HttpResponse(simplejson.dumps(d), mimetype="application/json")

    def changelist_view(self, request, extra_context=None, *args, **kwargs):
        """
        Handle the changelist view, the django view for the model instances
        change list/actions page.
        """

        if 'actions_column' not in self.list_display:
            self.list_display.append('actions_column')

        # handle common AJAX requests
        if request.is_ajax():
            cmd = request.POST.get('__cmd')
            if cmd == 'toggle_boolean':
                return self._toggle_boolean(request)
            elif cmd == 'move_node':
                return self._move_node(request)
            else:
                return HttpResponseBadRequest('Oops. AJAX request not understood.')

        self._refresh_changelist_caches()

        extra_context = extra_context or {}
        extra_context['FEINCMS_ADMIN_MEDIA'] = settings.FEINCMS_ADMIN_MEDIA
        extra_context['FEINCMS_ADMIN_MEDIA_HOTLINKING'] = settings.FEINCMS_ADMIN_MEDIA_HOTLINKING
        extra_context['tree_structure'] = mark_safe(simplejson.dumps(
                                                    _build_tree_structure(self.model)))

        return super(TreeEditor, self).changelist_view(request, extra_context, *args, **kwargs)

    def _move_node(self, request):
        cut_item = self.model._tree_manager.get(pk=request.POST.get('cut_item'))
        pasted_on = self.model._tree_manager.get(pk=request.POST.get('pasted_on'))
        position = request.POST.get('position')

        if position in ('last-child', 'left'):
            self.model._tree_manager.move_node(cut_item, pasted_on, position)

            # Ensure that model save has been run
            source = self.model._tree_manager.get(pk=request.POST.get('cut_item'))
            source.save()

            return HttpResponse('OK')
        return HttpResponse('FAIL')

    def queryset(self, request):
        """
        Returns a QuerySet of all model instances that can be edited by the
        admin site. This is used by changelist_view.
        """
        qs = self.model._default_manager.get_query_set()
        qs.__class__ = TreeEditorQuerySet
        return qs

    def _actions_column(self, page):
        actions = []
        actions.append(u'<big>✂</big>' % (
            page.pk, _('Cut')))

        actions.append(u'↳' % (
            page.pk, _('Insert as child')))
        actions.append(u'↱' % (
            page.pk, _('Insert before')))
        return actions

    def actions_column(self, page):
        return u' '.join(self._actions_column(page))
    actions_column.allow_tags = True
    actions_column.short_description = _('actions')

<commit_msg>TreeEditor: Embed the url of the page in the change list so it can be retrieved by eg. tinymce.<commit_after>from django.conf import settings as django_settings
from django.contrib import admin
from django.db.models.query import QuerySet
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, HttpResponseServerError
from django.utils import simplejson
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _

from feincms import settings

import logging

# ------------------------------------------------------------------------
def django_boolean_icon(field_val, alt_text=None, title=None):
    """
    Return HTML code for a nice representation of true/false.
    """

    # Origin: contrib/admin/templatetags/admin_list.py
    BOOLEAN_MAPPING = { True: 'yes', False: 'no', None: 'unknown' }
    alt_text = alt_text or BOOLEAN_MAPPING[field_val]
    if title is not None:
        title = 'title="%s" ' % title
    else:
        title = ''
    return mark_safe(u'' %
            (django_settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], alt_text, title))

def _build_tree_structure(cls):
    """
    Build an in-memory representation of the item tree, trying to keep
    database accesses down to a minimum. The returned dictionary looks like
    this (as json dump):

        {"6": {"id": 6, "children": [7, 8, 10], "parent": null, "descendants": [7, 12, 13, 8, 10]},
         "7": {"id": 7, "children": [12], "parent": 6, "descendants": [12, 13]},
         "8": {"id": 8, "children": [], "parent": 6, "descendants": []},
         ...

    """
    all_nodes = { }
    def add_as_descendant(n, p):
        all_nodes[n]['descendants'].append(p)

        n_parent_id = all_nodes[n]['parent']

        if n_parent_id:
            add_as_descendant(n_parent_id, p)

    for p_id, parent_id in cls.objects.order_by(cls._meta.tree_id_attr, cls._meta.left_attr).values_list("pk", "%s_id" % cls._meta.parent_attr):

        all_nodes[p_id] = { 'id': p_id, 'children' : [ ], 'descendants' : [ ], 'parent' : parent_id }

        if parent_id:
            all_nodes[parent_id]['children'].append(p_id)
            add_as_descendant(parent_id, p_id)

    return all_nodes

# ------------------------------------------------------------------------
def ajax_editable_boolean_cell(item, attr, text='', override=None):
    """
    Generate a html snippet for showing a boolean value on the admin page.
    Item is an object, attr is the attribute name we should display. Text
    is an optional explanatory text to be included in the output.

    This function will emit code to produce a checkbox input with its state
    corresponding to the item.attr attribute if no override value is passed.
    This input is wired to run a JS ajax updater to toggle the value.

    If override is passed in, ignores the attr attribute and returns a
    static image for the override boolean with no user interaction possible
    (useful for "disabled and you can't change it" situations).
    """
    if text:
        text = ' (%s)' % unicode(text)

    if override is not None:
        a = [ django_boolean_icon(override, text), text ]
    else:
        value = getattr(item, attr)
        a = [
              '<input type="checkbox"',
              value and ' checked="checked"' or '',
              ' onclick="return inplace_toggle_boolean(%d, \'%s\')";' % (item.id, attr),
              ' />',
              text,
            ]

    a.insert(0, '
' % ( attr, item.id ))
    a.append('
')
    #print a
    return unicode(''.join(a))

# ------------------------------------------------------------------------
def ajax_editable_boolean(attr, short_description):
    """
    Convenience function: Assign the return value of this method to a variable
    of your ModelAdmin class and put the variable name into list_display.

    Example:
        class MyTreeEditor(TreeEditor):
            list_display = ('__unicode__', 'active_toggle')

            active_toggle = ajax_editable_boolean('active', _('is active'))
    """
    def _fn(self, item):
        return ajax_editable_boolean_cell(item, attr)
    _fn.allow_tags = True
    _fn.short_description = short_description
    _fn.editable_boolean_field = attr
    return _fn

# ------------------------------------------------------------------------
class TreeEditorQuerySet(QuerySet):
    """
    The TreeEditorQuerySet is a special query set used only in the TreeEditor
    ChangeList page. The only difference to a regular QuerySet is that it
    will enforce:

        (a) The result is ordered in correct tree order so that
            the TreeAdmin works all right.

        (b) It ensures that all ancestors of selected items are included
            in the result set, so the resulting tree display actually
            makes sense.
    """
    def iterator(self):
        qs = self
        # Reaching into the bowels of query sets to find out whether the qs is
        # actually filtered and we need to do the INCLUDE_ANCESTORS dance at all.
        # INCLUDE_ANCESTORS is quite expensive, so don't do it if not needed.
        is_filtered = bool(qs.query.where.children)
        if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS and is_filtered:
            include_pages = set()
            # Order by 'rght' will return the tree deepest nodes first;
            # this cuts down the number of queries considerably since all ancestors
            # will already be in include_pages when they are checked, thus not
            # trigger additional queries.
            for p in super(TreeEditorQuerySet, self.order_by('rght')).iterator():
                if p.parent_id and p.parent_id not in include_pages and \
                                   p.id not in include_pages:
                    ancestor_id_list = p.get_ancestors().values_list('id', flat=True)
                    include_pages.update(ancestor_id_list)

            if include_pages:
                qs = qs | self.model._default_manager.filter(id__in=include_pages)

            qs = qs.distinct()

        for obj in super(TreeEditorQuerySet, qs).iterator():
            yield obj

    if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS:
        def __getitem__(self, index):
            return self   # Don't even try to slice

        def get(self, *args, **kwargs):
            """
            Quick and dirty hack to fix change_view and delete_view; they use
            self.queryset(request).get(...) to get the object they should work
            with. Our modifications to the queryset when INCLUDE_ANCESTORS is
            enabled make get() fail often with a MultipleObjectsReturned
            exception.
            """
            return self.model._default_manager.get(*args, **kwargs)

# !!!: Hack alert! Patching ChangeList, check whether this still applies post Django 1.1
# If the ChangeList is used by a TreEditor, we always need to order by 'tree_id' and 'lft'.
from django.contrib.admin.views import main
class ChangeList(main.ChangeList):
    def get_query_set(self):
        qs = super(ChangeList, self).get_query_set()
        if isinstance(self.model_admin, TreeEditor):
            return qs.order_by('tree_id', 'lft')
        return qs
main.ChangeList = ChangeList

# ------------------------------------------------------------------------
# MARK: -
# ------------------------------------------------------------------------

class TreeEditor(admin.ModelAdmin):
    if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS:
        # Make sure that no pagination is displayed. Slicing is disabled anyway,
        # therefore this value does not have an influence on the queryset
        list_per_page = 999999999

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

        self.list_display = list(self.list_display)

        if 'indented_short_title' not in self.list_display:
            if self.list_display[0] == 'action_checkbox':
                self.list_display[1] = 'indented_short_title'
            else:
                self.list_display[0] = 'indented_short_title'
        self.list_display_links = ('indented_short_title',)

        opts = self.model._meta
        self.change_list_template = [
            'admin/feincms/%s/%s/tree_editor.html' % (opts.app_label, opts.object_name.lower()),
            'admin/feincms/%s/tree_editor.html' % opts.app_label,
            'admin/feincms/tree_editor.html',
            ]

    def indented_short_title(self, item):
        """
        Generate a short title for a page, indent it depending on
        the page's depth in the hierarchy.
        """
        r = '''<input type="hidden" class="medialibrary_file_path" value="%s">  ''' % (
                item.get_absolute_url(),
                item.id, item.id, 14+item.level*18)

#        r += ''
        if hasattr(item, 'short_title'):
            r += item.short_title()
        else:
            r += unicode(item)
#        r += ''
        return mark_safe(r)
    indented_short_title.short_description = _('title')
    indented_short_title.allow_tags = True

    def _collect_editable_booleans(self):
        """
        Collect all fields marked as editable booleans. We do not
        want the user to be able to edit arbitrary fields by crafting
        an AJAX request by hand.
        """
        if hasattr(self, '_ajax_editable_booleans'):
            return

        self._ajax_editable_booleans = {}

        for field in self.list_display:
            # The ajax_editable_boolean return value has to be assigned
            # to the ModelAdmin class
            item = getattr(self.__class__, field, None)
            if not item:
                continue

            attr = getattr(item, 'editable_boolean_field', None)
            if attr:
                def _fn(self, page):
                    return [ ajax_editable_boolean_cell(page, _fn.attr) ]
                _fn.attr = attr
                result_func = getattr(item, 'editable_boolean_result', _fn)
                self._ajax_editable_booleans[attr] = result_func

    def _refresh_changelist_caches(self):
        """
        Refresh information used to show the changelist tree structure such as
        inherited active/inactive states etc.

        XXX: This is somewhat hacky, but since it's an internal method, so be it.
        """

        pass

    def _toggle_boolean(self, request):
        """
        Handle an AJAX toggle_boolean request
        """
        try:
            item_id = int(request.POST.get('item_id', None))
            attr = str(request.POST.get('attr', None))
        except:
            return HttpResponseBadRequest("Malformed request")

        if not request.user.is_staff:
            logging.warning("Denied AJAX request by non-staff %s to toggle boolean %s for page #%s", request.user, attr, item_id)
            return HttpResponseForbidden("You do not have permission to access this page")

        self._collect_editable_booleans()

        if not self._ajax_editable_booleans.has_key(attr):
            return HttpResponseBadRequest("not a valid attribute %s" % attr)

        try:
            obj = self.model._default_manager.get(pk=item_id)
        except self.model.DoesNotExist:
            return HttpResponseNotFound("Object does not exist")

        can_change = False

        if hasattr(obj, "user_can") and obj.user_can(request.user, change_page=True):
            can_change = True
        else:
            can_change = request.user.has_perm("page.change_page")

        if not can_change:
            logging.warning("Denied AJAX request by %s to toggle boolean %s for page %s", request.user, attr, item_id)
            return HttpResponseForbidden("You do not have permission to access this page")

        logging.info("Processing request by %s to toggle %s on %s", request.user, attr, obj)

        try:
            before_data = self._ajax_editable_booleans[attr](self, obj)

            setattr(obj, attr, not getattr(obj, attr))
            obj.save()

            self._refresh_changelist_caches() # ???: Perhaps better a post_save signal?

            # Construct html snippets to send back to client for status update
            data = self._ajax_editable_booleans[attr](self, obj)

        except Exception, e:
            logging.exception("Unhandled exception while toggling %s on %s", attr, obj)
            return HttpResponseServerError("Unable to toggle %s on %s" % (attr, obj))

        # Weed out unchanged cells to keep the updates small. This assumes
        # that the order a possible get_descendents() returns does not change
        # before and after toggling this attribute. Unlikely, but still...
        d = []
        for a, b in zip(before_data, data):
            if a != b:
                d.append(b)

        return HttpResponse(simplejson.dumps(d), mimetype="application/json")

    def changelist_view(self, request, extra_context=None, *args, **kwargs):
        """
        Handle the changelist view, the django view for the model instances
        change list/actions page.
        """

        if 'actions_column' not in self.list_display:
            self.list_display.append('actions_column')

        # handle common AJAX requests
        if request.is_ajax():
            cmd = request.POST.get('__cmd')
            if cmd == 'toggle_boolean':
                return self._toggle_boolean(request)
            elif cmd == 'move_node':
                return self._move_node(request)
            else:
                return HttpResponseBadRequest('Oops. AJAX request not understood.')

        self._refresh_changelist_caches()

        extra_context = extra_context or {}
        extra_context['FEINCMS_ADMIN_MEDIA'] = settings.FEINCMS_ADMIN_MEDIA
        extra_context['FEINCMS_ADMIN_MEDIA_HOTLINKING'] = settings.FEINCMS_ADMIN_MEDIA_HOTLINKING
        extra_context['tree_structure'] = mark_safe(simplejson.dumps(
                                                    _build_tree_structure(self.model)))

        return super(TreeEditor, self).changelist_view(request, extra_context, *args, **kwargs)

    def _move_node(self, request):
        cut_item = self.model._tree_manager.get(pk=request.POST.get('cut_item'))
        pasted_on = self.model._tree_manager.get(pk=request.POST.get('pasted_on'))
        position = request.POST.get('position')

        if position in ('last-child', 'left'):
            self.model._tree_manager.move_node(cut_item, pasted_on, position)

            # Ensure that model save has been run
            source = self.model._tree_manager.get(pk=request.POST.get('cut_item'))
            source.save()

            return HttpResponse('OK')
        return HttpResponse('FAIL')

    def queryset(self, request):
        """
        Returns a QuerySet of all model instances that can be edited by the
        admin site. This is used by changelist_view.
        """
        qs = self.model._default_manager.get_query_set()
        qs.__class__ = TreeEditorQuerySet
        return qs

    def _actions_column(self, page):
        actions = []
        actions.append(u'<big>✂</big>' % (
            page.pk, _('Cut')))

        actions.append(u'↳' % (
            page.pk, _('Insert as child')))
        actions.append(u'↱' % (
            page.pk, _('Insert before')))
        return actions

    def actions_column(self, page):
        return u' '.join(self._actions_column(page))
    actions_column.allow_tags = True
    actions_column.short_description = _('actions')

<|endoftext|>


<commit_before>"""
sinecosine.py
Author: Dina
Credit: <list sources used, if any>

Assignment:

In this assignment you must use *list comprehensions* to generate sprites that show the behavior
of certain mathematical functions: sine and cosine.

The sine and cosine functions are provided in the Python math library. These functions are used
to relate *angles* to *rectangular* (x,y) coordinate systems and can be very useful in computer
game design.

Unlike the last assignment using ggame`, this one will not provide any "skeleton" code to fill
in. You should use your submission for the Picture assignment
(https://github.com/HHS-IntroProgramming/Picture) as a reference for starting this assignment.

See:
https://github.com/HHS-IntroProgramming/Sine-Cosine/blob/master/README.md
for a detailed list of requirements for this assignment.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Displaying-Graphics
for general information on how to use ggame.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Programmed-Graphics
for general information on using list comprehensions to generate graphics.

http://brythonserver.github.io/ggame/
for detailed information on ggame.
"""
from ggame import App, Color, LineStyle, Sprite, CircleAsset
import math

blue = Color(0x0000ff, 1.0)
red = Color(0xff0000, 1.0)
purple = Color(0xff00ff, 1.0)
black = Color(0x000000, 1.0)
thinline = LineStyle(1, black)

circle1= CircleAsset(10, thinline, blue)
circle2= CircleAsset(10, thinline, red)
circle3= CircleAsset(10, thinline, purple)
Sprite(circle3, (470, 250))

list(range(0, 360, 10)
x1(100+100*sin(radians(x)))

Sprite(circle1, (x1, 444))
Sprite(circle2, (111, 111))
Sprite(circle3, (666,666))

myapp = App()
myapp.run()<commit_msg>Updated from Brython Server: 9/18/2015 2:50:30 PM<commit_after>"""
sinecosine.py
Author: Dina
Credit: <list sources used, if any>

Assignment:

In this assignment you must use *list comprehensions* to generate sprites that show the behavior
of certain mathematical functions: sine and cosine.

The sine and cosine functions are provided in the Python math library. These functions are used
to relate *angles* to *rectangular* (x,y) coordinate systems and can be very useful in computer
game design.

Unlike the last assignment using ggame`, this one will not provide any "skeleton" code to fill
in. You should use your submission for the Picture assignment
(https://github.com/HHS-IntroProgramming/Picture) as a reference for starting this assignment.

See:
https://github.com/HHS-IntroProgramming/Sine-Cosine/blob/master/README.md
for a detailed list of requirements for this assignment.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Displaying-Graphics
for general information on how to use ggame.

https://github.com/HHS-IntroProgramming/Standards-and-Syllabus/wiki/Programmed-Graphics
for general information on using list comprehensions to generate graphics.

http://brythonserver.github.io/ggame/
for detailed information on ggame.
"""
from ggame import App, Color, LineStyle, Sprite, CircleAsset
from math import sin, cos, radians

blue = Color(0x0000ff, 1.0)
red = Color(0xff0000, 1.0)
purple = Color(0xff00ff, 1.0)
black = Color(0x000000, 1.0)
thinline = LineStyle(1, black)

circle1= CircleAsset(10, thinline, blue)
circle2= CircleAsset(10, thinline, red)
circle3= CircleAsset(10, thinline, purple)
Sprite(circle3, (470, 250))

x = (range(0, 360, 10))
x1 = (100+100*sin(radians(x)))

Sprite(circle1, (x1, 444))
Sprite(circle2, (111, 111))
Sprite(circle3, (666,666))

myapp = App()
myapp.run()<|endoftext|>


<commit_before>#! /usr/bin/env python

# Creates ENFEs and convergence figures for BBOB post-processing.

import os
import sys
import matplotlib.pyplot as plt
import numpy
from pdb import set_trace
from bbob_pproc import bootstrap

maxEvalsFactor = 1e6

#valuesOfInterest = (1.0, 1.0e-2, 1.0e-4, 1.0e-6, 1.0e-8)
#colors = {1.0:'b', 1.0e-2:'g', 1.0e-4:'r', 1.0e-6:'c', 1.0e-8:'m'} #TODO colormaps!
colors = ('g', 'c', 'b', 'r', 'm', 'g', 'c', 'b', 'r', 'm')  # should not be too short
# should correspond with the colors in pprldistr.

#Get benchmark short infos.
funInfos = {}
isBenchmarkinfosFound = True
try:
    infofile = os.path.join(os.path.split(__file__)[0], '..', '..',
                            'benchmarkshortinfos')
    f = open(infofile,'r')
    for line in f:
        if len(line) == 0 or line.startswith('%') or line.isspace() :
            continue
        funcId, funcInfo = line[0:-1].split(None,1)
        funInfos[int(funcId)] = funcId + ' ' + funcInfo
    f.close()
except IOError, (errno, strerror):
    print "I/O error(%s): %s" % (errno, strerror)
    isBenchmarkinfosFound = False
    print 'Could not find benchmarkshortinfos file. '\
          'Titles in scaling figures will not be displayed.'

def createFigure(data, figHandle=None):
    """ Create a plot in a figure (eventually new) from a data array.

        Mandatory input:
        data - 2d-array where the 1st column contains x-values
               and the remaining columns contain y-values

        Optional inputs:
        figHandle - handle to existing figure, if omitted a new figure
                    will be created

        Output:
        lines - handles of the Line2D instances.

    """

    # initialize figure and handles
    if figHandle is None:
        figHandle = plt.figure()
    else:
        plt.figure(figHandle.number)

    lines = []
    # Plot data sets
    for i in range(1,len(data[0,:])):
        yValues = data[:,i]

        # Plot figure
        tmp = numpy.where(numpy.isfinite(data[:,i]))[0]
        if tmp.size > 0:
            lines.append(plt.plot(data[tmp,0], yValues[tmp]))
            #lines.append(plt.plot([data[tmp[-1],0]], [yValues[tmp[-1]]],
                                  #marker='+'))
            #TODO: larger size.
        #else: ???

    return lines

def customizeFigure(figHandle, figureName = None, title='',
                    fileFormat=('png','eps'), labels=None,
                    scale=['linear','linear'], legend=True,
                    locLegend='best', verbose=True):
    """ Customize a figure by adding a legend, axis label, etc. At the
        end the figure is saved.

        Inputs:
        figHandle - handle to existing figure
        figureName - name of the output figure

        Optional Inputs:
        fileFormat - list of formats of output files
        labels - list with xlabel and ylabel
        scale - scale for x-axis and y-axis
        legend - show legend
        locLegend - location of legend

    """

    # Input checking

    # Get axis handle and set scale for each axis
    axisHandle = figHandle.gca()
    axisHandle.set_xscale(scale[0])
    axisHandle.set_yscale(scale[1])

    # Annotate figure
    if labels is not None: #Couldn't it be ''?
        axisHandle.set_xlabel(labels[0])
        axisHandle.set_ylabel(labels[1])

    # Grid options
    axisHandle.grid('True')
    ylim_org = axisHandle.get_ylim()
    # linear and quadratic "grid"
    plt.plot((2,200), (1,1e2), 'k:')    # TODO: this should be done before the real lines are plotted?
    plt.plot((2,200), (1,1e4), 'k:')
    plt.plot((2,200), (1e3,1e5), 'k:')  # yet experimental
    plt.plot((2,200), (1e3,1e7), 'k:')

    # axes limites
    axisHandle.set_xlim(1.8, 45)                # TODO should become input arg?
    axisHandle.set_ylim(10**-0.2, ylim_org[1])

    # ticks on axes
    #axisHandle.invert_xaxis()
    dimticklist = (2, 3, 4, 5, 10, 20, 40)  # TODO: should become input arg at some point?
    dimannlist = (2, 3, '', 5, 10, 20, 40)  # TODO: should become input arg at some point?
    axisHandle.set_xticks(dimticklist)
    axisHandle.set_xticklabels([str(n) for n in dimannlist])

    tmp = axisHandle.get_yticks()
    tmp2 = []
    for i in tmp:
        tmp2.append('%d' % round(numpy.log10(i)))
    axisHandle.set_yticklabels(tmp2)

    # Legend
    if legend:
        plt.legend(loc=locLegend)
    #if len(legend) > 0:
        #if len(legendh) > 0:
            #axisHandle.legend(legendh, legend, locLegend)
        #else:
            #axisHandle.legend(legend, locLegend)
    axisHandle.set_title(title)

    # Save figure
    if not (figureName is None or fileFormat is None):
        if isinstance(fileFormat, basestring):
            plt.savefig(figureName + '.' + fileFormat, dpi = 120,
                        format = entry)
            if verbose:
                print 'Wrote figure in %s.' %(figureName + '.' + fileFormat)
        else:
            #TODO: that is if fileFormat is iterable.
            for entry in fileFormat:
                plt.savefig(figureName + '.' + entry, dpi = 120,
                            format = entry)
                if verbose:
                    print 'Wrote figure in %s.' %(figureName + '.' + entry)

    # TODO:    *much more options available (styles, colors, markers ...)
    #       *output directory - contained in the file name or extra parameter?

def sortIndexEntries(indexEntries):
    """From a list of IndexEntry, returns a sorted dictionary."""
    sortByFunc = {}
    dims = set()
    funcs = set()
    for elem in indexEntries:
        sortByFunc.setdefault(elem.funcId,{})
        sortByFunc[elem.funcId][elem.dim] = elem
        funcs.add(elem.funcId)
        dims.add(elem.dim)

    return sortByFunc

def generateData(indexEntry, targetFuncValue):
    """Returns data to be plotted."""

    res = []

    for i in indexEntry.hData:
        if i[0] <= targetFuncValue:
            tmp = []
            for j in i[indexEntry.nbRuns+1:]:
                tmp.append(j <= i[0])
            res.extend(bootstrap.sp(i[1:indexEntry.nbRuns + 1],
                                     issuccessful=tmp))
            res.append(res[0] * max(res[2], 1)) #Sum(FE)
            res.append(bootstrap.prctile(i[1:indexEntry.nbRuns + 1], 50)[0])
            break

    # if targetFuncValue was not reached
    if not res and len(indexEntry.vData) > 0:
        it = iter(indexEntry.vData)
        i = it.next()

        try:
            while i[0] <= maxEvalsFactor * indexEntry.dim:
                i = it.next()
        except StopIteration:
            pass
        #res = [numpy.sum(i[1:indexEntry.nbRuns + 1]), 0., 0,
               #bootstrap.prctile(i[1:indexEntry.nbRuns + 1], 50)[0]]
        res.extend(bootstrap.sp(i[1:indexEntry.nbRuns + 1],
                                issuccessful=[False]*indexEntry.nbRuns))
        res.append(res[0] * max(res[2], 1)) #Sum(FE)
        res.append(bootstrap.prctile(i[1:indexEntry.nbRuns + 1], 50)[0])

    return numpy.array(res)

def main(indexEntries, valuesOfInterest, outputdir, verbose=True):
    """From a list of IndexEntry, returns a convergence and ENFEs figure vs dim

    """

    plt.rc("axes", labelsize=20, titlesize=24)
    plt.rc("xtick", labelsize=20)
    plt.rc("ytick", labelsize=20)
    plt.rc("font", size=20)
    plt.rc("legend", fontsize=20)

    sortByFunc = sortIndexEntries(indexEntries)
    for func in sortByFunc:
        filename = os.path.join(outputdir,'ppdata_f%d' % (func))
        fig = plt.figure()
        #legend = []
        line = []
        for i in range(len(valuesOfInterest)):
            #data = []
            succ = []
            unsucc = []
            data = []
            #Collect data from indexEntry that have the same function and
            #different dimension.
            for dim in sorted(sortByFunc[func]):
                tmp = generateData(sortByFunc[func][dim], valuesOfInterest[i])
                data.append(numpy.append(dim, tmp))
                if tmp[2] > 0: #Number of success is larger than 0
                    succ.append(numpy.append(dim, tmp))
                else:
                    unsucc.append(numpy.append(dim, tmp))

            if succ:
                tmp = numpy.vstack(succ)
                h = createFigure(tmp[:, [0, 1]], fig) #ERT
                plt.setp(h[0], 'color', colors[i], 'linestyle', '',
                         'marker', 'o', 'markersize', 20)
                h = createFigure(tmp[:,[0, -1]], fig) #median
                plt.setp(h[0], 'color', colors[i], 'linestyle', '',
                         'marker', '+', 'markersize', 30, 'markeredgewidth', 5)

            if unsucc:
                tmp = numpy.vstack(unsucc)
                h = createFigure(tmp[:, [0, 1]], fig) #ERT
                plt.setp(h[0], 'color', colors[i], 'linestyle', '',
                         'marker', 'x', 'markersize', 20)

            if data:
                tmp = numpy.vstack(data)
                h = createFigure(tmp[:,[0, 1]], fig) #ERT
                plt.setp(h[0], 'color', colors[i], 'label',
                         ' %+d' % (numpy.log10(valuesOfInterest[i])))
                line.extend(h[0])

        if isBenchmarkinfosFound:
            title = funInfos[func]
        else:
            title = ''

        #if func > 1:  # legends hiding points can be very annoying
            #legend = ''

        legend = func in (1, 101)

        customizeFigure(fig, filename, title=title, legend=legend,
                        fileFormat=('eps','png'), labels=['', ''],
                        scale=['log','log'], verbose=verbose)

        plt.close(fig)

        plt.rcdefaults()

    # TODO: how do we make a user define what color or line style?
<commit_msg>BUG FIX: Shrunken figures annotations.<commit_after>#! /usr/bin/env python

# Creates ENFEs and convergence figures for BBOB post-processing.

import os
import sys
import matplotlib.pyplot as plt
import numpy
from pdb import set_trace
from bbob_pproc import bootstrap

maxEvalsFactor = 1e6

#valuesOfInterest = (1.0, 1.0e-2, 1.0e-4, 1.0e-6, 1.0e-8)
#colors = {1.0:'b', 1.0e-2:'g', 1.0e-4:'r', 1.0e-6:'c', 1.0e-8:'m'} #TODO colormaps!
colors = ('g', 'c', 'b', 'r', 'm', 'g', 'c', 'b', 'r', 'm')  # should not be too short
# should correspond with the colors in pprldistr.

#Get benchmark short infos.
funInfos = {}
isBenchmarkinfosFound = True
try:
    infofile = os.path.join(os.path.split(__file__)[0], '..', '..',
                            'benchmarkshortinfos')
    f = open(infofile,'r')
    for line in f:
        if len(line) == 0 or line.startswith('%') or line.isspace() :
            continue
        funcId, funcInfo = line[0:-1].split(None,1)
        funInfos[int(funcId)] = funcId + ' ' + funcInfo
    f.close()
except IOError, (errno, strerror):
    print "I/O error(%s): %s" % (errno, strerror)
    isBenchmarkinfosFound = False
    print 'Could not find benchmarkshortinfos file. '\
          'Titles in scaling figures will not be displayed.'

def createFigure(data, figHandle=None):
    """ Create a plot in a figure (eventually new) from a data array.

        Mandatory input:
        data - 2d-array where the 1st column contains x-values
               and the remaining columns contain y-values

        Optional inputs:
        figHandle - handle to existing figure, if omitted a new figure
                    will be created

        Output:
        lines - handles of the Line2D instances.

    """

    # initialize figure and handles
    if figHandle is None:
        figHandle = plt.figure()
    else:
        plt.figure(figHandle.number)

    lines = []
    # Plot data sets
    for i in range(1,len(data[0,:])):
        yValues = data[:,i]

        # Plot figure
        tmp = numpy.where(numpy.isfinite(data[:,i]))[0]
        if tmp.size > 0:
            lines.append(plt.plot(data[tmp,0], yValues[tmp]))
            #lines.append(plt.plot([data[tmp[-1],0]], [yValues[tmp[-1]]],
                                  #marker='+'))
            #TODO: larger size.
        #else: ???

    return lines

def customizeFigure(figHandle, figureName = None, title='',
                    fileFormat=('png','eps'), labels=None,
                    scale=['linear','linear'], legend=True,
                    locLegend='best', verbose=True):
    """ Customize a figure by adding a legend, axis label, etc. At the
        end the figure is saved.

        Inputs:
        figHandle - handle to existing figure
        figureName - name of the output figure

        Optional Inputs:
        fileFormat - list of formats of output files
        labels - list with xlabel and ylabel
        scale - scale for x-axis and y-axis
        legend - show legend
        locLegend - location of legend

    """

    # Input checking

    # Get axis handle and set scale for each axis
    axisHandle = figHandle.gca()
    axisHandle.set_xscale(scale[0])
    axisHandle.set_yscale(scale[1])

    # Annotate figure
    if labels is not None: #Couldn't it be ''?
        axisHandle.set_xlabel(labels[0])
        axisHandle.set_ylabel(labels[1])

    # Grid options
    axisHandle.grid('True')
    ylim_org = axisHandle.get_ylim()
    # linear and quadratic "grid"
    plt.plot((2,200), (1,1e2), 'k:')    # TODO: this should be done before the real lines are plotted?
    plt.plot((2,200), (1,1e4), 'k:')
    plt.plot((2,200), (1e3,1e5), 'k:')  # yet experimental
    plt.plot((2,200), (1e3,1e7), 'k:')

    # axes limites
    axisHandle.set_xlim(1.8, 45)                # TODO should become input arg?
    axisHandle.set_ylim(10**-0.2, ylim_org[1])

    # ticks on axes
    #axisHandle.invert_xaxis()
    dimticklist = (2, 3, 4, 5, 10, 20, 40)  # TODO: should become input arg at some point?
    dimannlist = (2, 3, '', 5, 10, 20, 40)  # TODO: should become input arg at some point?
    axisHandle.set_xticks(dimticklist)
    axisHandle.set_xticklabels([str(n) for n in dimannlist])

    tmp = axisHandle.get_yticks()
    tmp2 = []
    for i in tmp:
        tmp2.append('%d' % round(numpy.log10(i)))
    axisHandle.set_yticklabels(tmp2)

    # Legend
    if legend:
        plt.legend(loc=locLegend)
    #if len(legend) > 0:
        #if len(legendh) > 0:
            #axisHandle.legend(legendh, legend, locLegend)
        #else:
            #axisHandle.legend(legend, locLegend)
    axisHandle.set_title(title)

    # Save figure
    if not (figureName is None or fileFormat is None):
        if isinstance(fileFormat, basestring):
            plt.savefig(figureName + '.' + fileFormat, dpi = 120,
                        format = entry)
            if verbose:
                print 'Wrote figure in %s.' %(figureName + '.' + fileFormat)
        else:
            #TODO: that is if fileFormat is iterable.
            for entry in fileFormat:
                plt.savefig(figureName + '.' + entry, dpi = 120,
                            format = entry)
                if verbose:
                    print 'Wrote figure in %s.' %(figureName + '.' + entry)

    # TODO:    *much more options available (styles, colors, markers ...)
    #       *output directory - contained in the file name or extra parameter?

def sortIndexEntries(indexEntries):
    """From a list of IndexEntry, returns a sorted dictionary."""
    sortByFunc = {}
    dims = set()
    funcs = set()
    for elem in indexEntries:
        sortByFunc.setdefault(elem.funcId,{})
        sortByFunc[elem.funcId][elem.dim] = elem
        funcs.add(elem.funcId)
        dims.add(elem.dim)

    return sortByFunc

def generateData(indexEntry, targetFuncValue):
    """Returns data to be plotted."""

    res = []

    for i in indexEntry.hData:
        if i[0] <= targetFuncValue:
            tmp = []
            for j in i[indexEntry.nbRuns+1:]:
                tmp.append(j <= i[0])
            res.extend(bootstrap.sp(i[1:indexEntry.nbRuns + 1],
                                     issuccessful=tmp))
            res.append(res[0] * max(res[2], 1)) #Sum(FE)
            res.append(bootstrap.prctile(i[1:indexEntry.nbRuns + 1], 50)[0])
            break

    # if targetFuncValue was not reached
    if not res and len(indexEntry.vData) > 0:
        it = iter(indexEntry.vData)
        i = it.next()

        try:
            while i[0] <= maxEvalsFactor * indexEntry.dim:
                i = it.next()
        except StopIteration:
            pass
        #res = [numpy.sum(i[1:indexEntry.nbRuns + 1]), 0., 0,
               #bootstrap.prctile(i[1:indexEntry.nbRuns + 1], 50)[0]]
        res.extend(bootstrap.sp(i[1:indexEntry.nbRuns + 1],
                                issuccessful=[False]*indexEntry.nbRuns))
        res.append(res[0] * max(res[2], 1)) #Sum(FE)
        res.append(bootstrap.prctile(i[1:indexEntry.nbRuns + 1], 50)[0])

    return numpy.array(res)

def main(indexEntries, valuesOfInterest, outputdir, verbose=True):
    """From a list of IndexEntry, returns a convergence and ENFEs figure vs dim

    """

    plt.rc("axes", labelsize=20, titlesize=24)
    plt.rc("xtick", labelsize=20)
    plt.rc("ytick", labelsize=20)
    plt.rc("font", size=20)
    plt.rc("legend", fontsize=20)

    sortByFunc = sortIndexEntries(indexEntries)
    for func in sortByFunc:
        filename = os.path.join(outputdir,'ppdata_f%d' % (func))
        fig = plt.figure()
        #legend = []
        line = []
        for i in range(len(valuesOfInterest)):
            #data = []
            succ = []
            unsucc = []
            data = []
            #Collect data from indexEntry that have the same function and
            #different dimension.
            for dim in sorted(sortByFunc[func]):
                tmp = generateData(sortByFunc[func][dim], valuesOfInterest[i])
                data.append(numpy.append(dim, tmp))
                if tmp[2] > 0: #Number of success is larger than 0
                    succ.append(numpy.append(dim, tmp))
                else:
                    unsucc.append(numpy.append(dim, tmp))

            if succ:
                tmp = numpy.vstack(succ)
                h = createFigure(tmp[:, [0, 1]], fig) #ERT
                plt.setp(h[0], 'color', colors[i], 'linestyle', '',
                         'marker', 'o', 'markersize', 20)
                h = createFigure(tmp[:,[0, -1]], fig) #median
                plt.setp(h[0], 'color', colors[i], 'linestyle', '',
                         'marker', '+', 'markersize', 30, 'markeredgewidth', 5)

            if unsucc:
                tmp = numpy.vstack(unsucc)
                h = createFigure(tmp[:, [0, 1]], fig) #ERT
                plt.setp(h[0], 'color', colors[i], 'linestyle', '',
                         'marker', 'x', 'markersize', 20)

            if data:
                tmp = numpy.vstack(data)
                h = createFigure(tmp[:,[0, 1]], fig) #ERT
                plt.setp(h[0], 'color', colors[i], 'label',
                         ' %+d' % (numpy.log10(valuesOfInterest[i])))
                line.extend(h[0])

        if isBenchmarkinfosFound:
            title = funInfos[func]
        else:
            title = ''

        #if func > 1:  # legends hiding points can be very annoying
            #legend = ''

        legend = func in (1, 101)

        customizeFigure(fig, filename, title=title, legend=legend,
                        fileFormat=('eps','png'), labels=['', ''],
                        scale=['log','log'], verbose=verbose)

        plt.close(fig)

    plt.rcdefaults()

    # TODO: how do we make a user define what color or line style?
<|endoftext|>


<commit_before>from django.shortcuts import redirect, get_object_or_404, render
from django.http import HttpResponse
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
from django.views import View
from django.views.generic.edit import UpdateView
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

from oauthlib.oauth1 import RequestValidator
from lti.contrib.django import DjangoToolProvider
from lti import ToolConfig

from opensubmit.social import lti
from django.conf import settings
from opensubmit.models import Assignment, LtiResult, Submission
from opensubmit.forms import getSubmissionForm
from opensubmit.views.frontend import SubmissionNewView, SubmissionWithdrawView

import sys
import logging
logger = logging.getLogger('OpenSubmit')

# Enable OAuth console logging for debugging purposes
oauth_log = logging.getLogger('oauthlib')
oauth_log.addHandler(logging.StreamHandler(sys.stdout))
oauth_log.setLevel(logging.DEBUG)

class LtiRequestValidator(RequestValidator):
    '''
        OAuth validator that considers Moodle and Opal LMS specifics,
        and relates the auth session to a particular course and its
        LTI credentials

        Dummy client handling as described in
        http://oauthlib.readthedocs.io/en/latest/_modules/oauthlib/oauth1/rfc5849/request_validator.html
    '''
    dummy_client = 'NiemandWuerdeEinenDeutschenLtiKeyAnlegenDerSoLangIstAlsoEinGuterDummy'
    client_key_length = (20, 100)   # relax default restrictions
    nonce_length = (20, 100)        # relax default restrictions
    # relax default restrictions, sometimes Moodle OAuth timestamps are too long ago
    timestamp_lifetime = 5000

    def __init__(self, assignment_id):
        super().__init__()
        self.assignment = get_object_or_404(Assignment, pk=assignment_id)

    def get_client_secret(self, client_key, request):
        if client_key == self.dummy_client:
            return self.dummy_client + 'MitSecret'
        else:
            return self.assignment.course.lti_secret

    @property
    def enforce_ssl(self):
        if settings.DEBUG:
            # for test suite runs
            return False
        else:
            return True

    def validate_timestamp_and_nonce(self, client_key, timestamp, nonce, request, request_token=None, access_token=None):
        # TODO: Store nonces in the database and take replay attacks seriously.
        return True

    def validate_client_key(self, client_key, request):
        return self.assignment.course.lti_key == client_key

def store_report_link(backend, user, response, *args, **kwargs):
    '''
    Part of the Python Social Auth Pipeline.
    Stores the result service URL reported by the LMS / LTI tool consumer so that we can use it later.
    '''
    if backend.name is 'lti':
        assignment_pk = response.get('assignment_pk', None)
        assignment = get_object_or_404(Assignment, pk=assignment_pk)
        lti_result, created = LtiResult.objects.get_or_create(assignment=assignment, user=user)
        if created:
            logger.debug("LTI result record not found, creating it.")
        else:
            logger.debug("LTI result record found, updating it.")   # Expected, check LTI standard
        lti_result.lis_result_sourcedid = response.get('lis_result_sourcedid')
        lti_result.lis_outcome_service_url = response.get('lis_outcome_service_url')
        lti_result.save()

@method_decorator(csrf_exempt, name='dispatch')
@method_decorator(xframe_options_exempt, name='dispatch')
class DispatcherView(View):
    '''
        "LMS Tool URL" view for providing OpenSubmit assignments through LTI.
    '''

    def get(self, request, pk):
        '''
        The LTI tool consumer performs a GET request when it wants to fetch the tool configuration.
        We create one LTI tool per assignment, which may sound strange. The reason is that the
        natural method of treating OpenSubmit as LTI tool, and giving a choice of assignments to the
        LTI tool consumer Moodle, works in theory form the protocol viewpoint, but not in Moodle
        correctly (TODO: Reference to according Moodle issue).
        '''
        launch_url = request.build_absolute_uri(
            reverse('lti', args=[pk]))
        assignment = get_object_or_404(Assignment, pk=pk)

        lti_tool_config = ToolConfig(
            title=assignment.title,
            launch_url=launch_url,
            secure_launch_url=launch_url)

        return HttpResponse(lti_tool_config.to_xml(), content_type='text/xml')

    def post(self, request, pk):
        '''
        The LTI tool provider uses POST to send a real, OAuth-signed, request for the LTI provider content.
        '''
        logger.debug("Incoming POST request through LTI")
        tool_provider = DjangoToolProvider.from_django_request(request=request)
        validator = LtiRequestValidator(pk)
        if tool_provider.is_valid_request(validator):
            logger.debug("Valid OAuth request through LTI")
            if request.user.is_authenticated:
                logger.debug("LTI consumer is already authenticated")
                return redirect(reverse('lti_submission', args=[pk]))
            else:
                logger.debug("LTI consumer needs OpenSubmit user, starting auth pipeline")
                # Store data being used by the Django Social Auth Provider for account creation
                data = request.POST.copy()
                data['assignment_pk'] = pk
                request.session[lti.SESSION_VAR] = data
                return redirect(reverse('social:begin', args=['lti']) + "?next=" + reverse('lti_submission', args=[pk]))
        else:
            logger.error("Invalid OAuth request through LTI")
            raise PermissionDenied

class WithdrawView(SubmissionWithdrawView):
    template_name = 'lti_withdraw.html'

    def get_success_url(self):
        submission = super(UpdateView, self).get_object()
        return reverse('lti_submission', args=[submission.assignment.pk])

class SubmissionView(SubmissionNewView):
    '''
    Special version of the submission dialogue for LTI
    integration.

    The POST logic is taken from the full 'new submission' view,
    everything else needs to be tailored for the LTI case.
    '''

    template_name = 'lti_new.html'
    redirect_on_success = '.'

    def dispatch(self, request, *args, **kwargs):
        self.ass = get_object_or_404(Assignment, pk=kwargs['pk'])
        self.submission = request.user.authored.all().filter(assignment=self.ass).exclude(state=Submission.WITHDRAWN)

        if self.submission:
            self.submission = self.submission[0]
            logger.debug("Found existing submission: " + str(self.submission))
        else:
            logger.debug("No submission found for this user.")
            # Check whether new submissions are allowed.
            if not self.ass.can_create_submission(user=request.user):
                logger.warn("User is not allowed to create a new submission.")
                raise PermissionDenied(
                    "You are not allowed to create a submission for this assignment")
            self.SubmissionForm = getSubmissionForm(self.ass)
        # continue with dispatching to get() / post() methods
        return super(TemplateView, self).dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        if self.submission:
            return render(request, 'lti_details.html', {'submission': self.submission})
        else:
            submissionForm = self.SubmissionForm(request.user, self.ass)
            return render(request, 'lti_new.html', {'submissionForm': submissionForm, 'assignment': self.ass})
<commit_msg>Try to fix mod_wsgi mulithreading race condition<commit_after>from django.shortcuts import redirect, get_object_or_404, render
from django.http import HttpResponse
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
from django.views import View
from django.views.generic.edit import UpdateView
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

from oauthlib.oauth1 import RequestValidator

from opensubmit.social import lti
from django.conf import settings
from opensubmit.models import Assignment, LtiResult, Submission
from opensubmit.forms import getSubmissionForm
from opensubmit.views.frontend import SubmissionNewView, SubmissionWithdrawView

import sys
import logging
logger = logging.getLogger('OpenSubmit')

# Enable OAuth console logging for debugging purposes
oauth_log = logging.getLogger('oauthlib')
oauth_log.addHandler(logging.StreamHandler(sys.stdout))
oauth_log.setLevel(logging.DEBUG)

class LtiRequestValidator(RequestValidator):
    '''
        OAuth validator that considers Moodle and Opal LMS specifics,
        and relates the auth session to a particular course and its
        LTI credentials

        Dummy client handling as described in
        http://oauthlib.readthedocs.io/en/latest/_modules/oauthlib/oauth1/rfc5849/request_validator.html
    '''
    dummy_client = 'NiemandWuerdeEinenDeutschenLtiKeyAnlegenDerSoLangIstAlsoEinGuterDummy'
    client_key_length = (20, 100)   # relax default restrictions
    nonce_length = (20, 100)        # relax default restrictions
    # relax default restrictions, sometimes Moodle OAuth timestamps are too long ago
    timestamp_lifetime = 5000

    def __init__(self, assignment_id):
        super().__init__()
        self.assignment = get_object_or_404(Assignment, pk=assignment_id)

    def get_client_secret(self, client_key, request):
        if client_key == self.dummy_client:
            return self.dummy_client + 'MitSecret'
        else:
            return self.assignment.course.lti_secret

    @property
    def enforce_ssl(self):
        if settings.DEBUG:
            # for test suite runs
            return False
        else:
            return True

    def validate_timestamp_and_nonce(self, client_key, timestamp, nonce, request, request_token=None, access_token=None):
        # TODO: Store nonces in the database and take replay attacks seriously.
        return True

    def validate_client_key(self, client_key, request):
        return self.assignment.course.lti_key == client_key

def store_report_link(backend, user, response, *args, **kwargs):
    '''
    Part of the Python Social Auth Pipeline.
    Stores the result service URL reported by the LMS / LTI tool consumer so that we can use it later.
    '''
    if backend.name is 'lti':
        assignment_pk = response.get('assignment_pk', None)
        assignment = get_object_or_404(Assignment, pk=assignment_pk)
        lti_result, created = LtiResult.objects.get_or_create(assignment=assignment, user=user)
        if created:
            logger.debug("LTI result record not found, creating it.")
        else:
            logger.debug("LTI result record found, updating it.")   # Expected, check LTI standard
        lti_result.lis_result_sourcedid = response.get('lis_result_sourcedid')
        lti_result.lis_outcome_service_url = response.get('lis_outcome_service_url')
        lti_result.save()

@method_decorator(csrf_exempt, name='dispatch')
@method_decorator(xframe_options_exempt, name='dispatch')
class DispatcherView(View):
    '''
        "LMS Tool URL" view for providing OpenSubmit assignments through LTI.
    '''

    def get(self, request, pk):
        '''
        The LTI tool consumer performs a GET request when it wants to fetch the tool configuration.
        We create one LTI tool per assignment, which may sound strange. The reason is that the
        natural method of treating OpenSubmit as LTI tool, and giving a choice of assignments to the
        LTI tool consumer Moodle, works in theory form the protocol viewpoint, but not in Moodle
        correctly (TODO: Reference to according Moodle issue).
        '''
        launch_url = request.build_absolute_uri(
            reverse('lti', args=[pk]))
        assignment = get_object_or_404(Assignment, pk=pk)

        from lti import ToolConfig
        lti_tool_config = ToolConfig(
            title=assignment.title,
            launch_url=launch_url,
            secure_launch_url=launch_url)

        return HttpResponse(lti_tool_config.to_xml(), content_type='text/xml')

    def post(self, request, pk):
        '''
        The LTI tool provider uses POST to send a real, OAuth-signed, request for the LTI provider content.
        '''
        logger.debug("Incoming POST request through LTI")
        from lti.contrib.django import DjangoToolProvider
        tool_provider = DjangoToolProvider.from_django_request(request=request)
        validator = LtiRequestValidator(pk)
        if tool_provider.is_valid_request(validator):
            logger.debug("Valid OAuth request through LTI")
            if request.user.is_authenticated:
                logger.debug("LTI consumer is already authenticated")
                return redirect(reverse('lti_submission', args=[pk]))
            else:
                logger.debug("LTI consumer needs OpenSubmit user, starting auth pipeline")
                # Store data being used by the Django Social Auth Provider for account creation
                data = request.POST.copy()
                data['assignment_pk'] = pk
                request.session[lti.SESSION_VAR] = data
                return redirect(reverse('social:begin', args=['lti']) + "?next=" + reverse('lti_submission', args=[pk]))
        else:
            logger.error("Invalid OAuth request through LTI")
            raise PermissionDenied

class WithdrawView(SubmissionWithdrawView):
    template_name = 'lti_withdraw.html'

    def get_success_url(self):
        submission = super(UpdateView, self).get_object()
        return reverse('lti_submission', args=[submission.assignment.pk])

class SubmissionView(SubmissionNewView):
    '''
    Special version of the submission dialogue for LTI
    integration.

    The POST logic is taken from the full 'new submission' view,
    everything else needs to be tailored for the LTI case.
    '''

    template_name = 'lti_new.html'
    redirect_on_success = '.'

    def dispatch(self, request, *args, **kwargs):
        self.ass = get_object_or_404(Assignment, pk=kwargs['pk'])
        self.submission = request.user.authored.all().filter(assignment=self.ass).exclude(state=Submission.WITHDRAWN)

        if self.submission:
            self.submission = self.submission[0]
            logger.debug("Found existing submission: " + str(self.submission))
        else:
            logger.debug("No submission found for this user.")
            # Check whether new submissions are allowed.
            if not self.ass.can_create_submission(user=request.user):
                logger.warn("User is not allowed to create a new submission.")
                raise PermissionDenied(
                    "You are not allowed to create a submission for this assignment")
            self.SubmissionForm = getSubmissionForm(self.ass)
        # continue with dispatching to get() / post() methods
        return super(TemplateView, self).dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        if self.submission:
            return render(request, 'lti_details.html', {'submission': self.submission})
        else:
            submissionForm = self.SubmissionForm(request.user, self.ass)
            return render(request, 'lti_new.html', {'submissionForm': submissionForm, 'assignment': self.ass})
<|endoftext|>


<commit_before>"""
Evennia settings file.

The available options are found in the default settings file found
here:

{settings_default}

Remember:

Don't copy more from the default file than you actually intend to
change; this will make sure that you don't overload upstream updates
unnecessarily.

When changing a setting requiring a file system path (like
path/to/actual/file.py), use GAME_DIR and EVENNIA_DIR to reference
your game folder and the Evennia library folders respectively. Python
paths (path.to.module) should be given relative to the game's root
folder (typeclasses.foo) whereas paths within the Evennia library
needs to be given explicitly (evennia.foo).

If you want to share your game dir, including its settings, you can
put secret game- or server-specific settings in secret_settings.py.

"""

# Use the defaults from Evennia unless explicitly overridden
from evennia.settings_default import *

######################################################################
# Evennia base server config
######################################################################

# This is the name of your game. Make it catchy!
SERVERNAME = {servername}

# Server ports. If enabled and marked as "visible", the port
# should be visible to the outside world on a production server.
# Note that there are many more options available beyond these.

# Telnet ports. Visible.
TELNET_ENABLED = True
TELNET_PORTS = [4000]
# (proxy, internal). Only proxy should be visible.
WEBSERVER_ENABLED = True
WEBSERVER_PORTS = [(4001, 4002)]
# Telnet+SSL ports, for supporting clients. Visible.
SSL_ENABLED = False
SSL_PORTS = [4003]
# SSH client ports. Requires crypto lib. Visible.
SSH_ENABLED = False
SSH_PORTS = [4004]
# Websocket-client port. Visible.
WEBSOCKET_CLIENT_ENABLED = True
WEBSOCKET_CLIENT_PORT = 4005
# Internal Server-Portal port. Not visible.
AMP_PORT = 4006

######################################################################
# Settings given in secret_settings.py override those in this file.
######################################################################
try:
    from server.conf.secret_settings import *
except ImportError:
    print "secret_settings.py file not found or failed to import."
<commit_msg>Stop hard-coding examples of port settings in game template, it just confuses things.<commit_after>"""
Evennia settings file.

The available options are found in the default settings file found
here:

{settings_default}

Remember:

Don't copy more from the default file than you actually intend to
change; this will make sure that you don't overload upstream updates
unnecessarily.

When changing a setting requiring a file system path (like
path/to/actual/file.py), use GAME_DIR and EVENNIA_DIR to reference
your game folder and the Evennia library folders respectively. Python
paths (path.to.module) should be given relative to the game's root
folder (typeclasses.foo) whereas paths within the Evennia library
needs to be given explicitly (evennia.foo).

If you want to share your game dir, including its settings, you can
put secret game- or server-specific settings in secret_settings.py.

"""

# Use the defaults from Evennia unless explicitly overridden
from evennia.settings_default import *

######################################################################
# Evennia base server config
######################################################################

# This is the name of your game. Make it catchy!
SERVERNAME = {servername}

######################################################################
# Settings given in secret_settings.py override those in this file.
######################################################################
try:
    from server.conf.secret_settings import *
except ImportError:
    print("secret_settings.py file not found or failed to import.")
<|endoftext|>