Commit 347d561f authored by frekk's avatar frekk
Browse files

moved csv stuff around a bit

parent f8df66f3
import csv
from collections import OrderedDict
from functools import wraps, singledispatch
from django.db.models import FieldDoesNotExist
from django.http import HttpResponse
def prep_field(obj, field):
"""
(for download_as_csv action)
Returns the field as a unicode string. If the field is a callable, it
attempts to call it first, without arguments.
"""
if '__' in field:
bits = field.split('__')
field = bits.pop()
for bit in bits:
obj = getattr(obj, bit, None)
if obj is None:
return ""
attr = getattr(obj, field)
output = attr() if callable(attr) else attr
return unicode(output).encode('utf-8') if output is not None else ""
@singledispatch
def download_as_csv(modeladmin, request, queryset):
"""
Generic csv export admin action.
Example:
class ExampleModelAdmin(admin.ModelAdmin):
raw_id_fields = ('field1',)
list_display = ('field1', 'field2', 'field3',)
actions = [download_as_csv,]
download_as_csv_fields = [
'field1',
('foreign_key1__foreign_key2__name', 'label2'),
('field3', 'label3'),
],
download_as_csv_header = True
"""
fields = getattr(modeladmin, 'download_as_csv_fields', None)
exclude = getattr(modeladmin, 'download_as_csv_exclude', None)
header = getattr(modeladmin, 'download_as_csv_header', True)
verbose_names = getattr(modeladmin, 'download_as_csv_verbose_names', True)
opts = modeladmin.model._meta
def fname(field):
if verbose_names:
return unicode(field.verbose_name).capitalize()
else:
return field.name
# field_names is a map of {field lookup path: field label}
if exclude:
field_names = OrderedDict(
(f.name, fname(f)) for f in opts.fields if f not in exclude
)
elif fields:
field_names = OrderedDict()
for spec in fields:
if isinstance(spec, (list, tuple)):
field_names[spec[0]] = spec[1]
else:
try:
f, _, _, _ = opts.get_field_by_name(spec)
except FieldDoesNotExist:
field_names[spec] = spec
else:
field_names[spec] = fname(f)
else:
field_names = OrderedDict(
(f.name, fname(f)) for f in opts.fields
)
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=%s.csv' % (
unicode(opts).replace('.', '_')
)
writer = csv.writer(response)
if header:
writer.writerow(field_names.values())
for obj in queryset:
writer.writerow([prep_field(obj, field) for field in field_names.keys()])
return response
download_as_csv.short_description = "Download selected objects as CSV file"
@download_as_csv.register(str)
def _(description):
"""
(overridden dispatcher)
Factory function for making a action with custom description.
Example:
class ExampleModelAdmin(admin.ModelAdmin):
raw_id_fields = ('field1',)
list_display = ('field1', 'field2', 'field3',)
actions = [download_as_csv("Export Special Report"),]
download_as_csv_fields = [
'field1',
('foreign_key1__foreign_key2__name', 'label2'),
('field3', 'label3'),
],
download_as_csv_header = True
"""
@wraps(download_as_csv)
def wrapped_action(modeladmin, request, queryset):
return download_as_csv(modeladmin, request, queryset)
wrapped_action.short_description = description
return wrapped_action
/*global gettext, interpolate, ngettext*/
(function($) {
'use strict';
var lastChecked;
$.fn.actions = function(opts) {
var options = $.extend({}, $.fn.actions.defaults, opts);
var actionCheckboxes = $(this);
var list_editable_changed = false;
var showQuestion = function() {
$(options.acrossClears).hide();
$(options.acrossQuestions).show();
$(options.allContainer).hide();
},
showClear = function() {
$(options.acrossClears).show();
$(options.acrossQuestions).hide();
$(options.actionContainer).toggleClass(options.selectedClass);
$(options.allContainer).show();
$(options.counterContainer).hide();
},
reset = function() {
$(options.acrossClears).hide();
$(options.acrossQuestions).hide();
$(options.allContainer).hide();
$(options.counterContainer).show();
},
clearAcross = function() {
reset();
$(options.acrossInput).val(0);
$(options.actionContainer).removeClass(options.selectedClass);
},
checker = function(checked) {
if (checked) {
showQuestion();
} else {
reset();
}
$(actionCheckboxes).prop("checked", checked)
.parent().parent().toggleClass(options.selectedClass, checked);
},
updateCounter = function() {
var sel = $(actionCheckboxes).filter(":checked").length;
// data-actions-icnt is defined in the generated HTML
// and contains the total amount of objects in the queryset
var actions_icnt = $('.action-counter').data('actionsIcnt');
$(options.counterContainer).html(interpolate(
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
sel: sel,
cnt: actions_icnt
}, true));
$(options.allToggle).prop("checked", function() {
var value;
if (sel === actionCheckboxes.length) {
value = true;
showQuestion();
} else {
value = false;
clearAcross();
}
return value;
});
};
// Show counter by default
$(options.counterContainer).show();
// Check state of checkboxes and reinit state if needed
$(this).filter(":checked").each(function(i) {
$(this).parent().parent().toggleClass(options.selectedClass);
updateCounter();
if ($(options.acrossInput).val() === 1) {
showClear();
}
});
$(options.allToggle).show().on('click', function() {
checker($(this).prop("checked"));
updateCounter();
});
$("a", options.acrossQuestions).on('click', function(event) {
event.preventDefault();
$(options.acrossInput).val(1);
showClear();
});
$("a", options.acrossClears).on('click', function(event) {
event.preventDefault();
$(options.allToggle).prop("checked", false);
clearAcross();
checker(0);
updateCounter();
});
lastChecked = null;
$(actionCheckboxes).on('click', function(event) {
if (!event) { event = window.event; }
var target = event.target ? event.target : event.srcElement;
if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) {
var inrange = false;
$(lastChecked).prop("checked", target.checked)
.parent().parent().toggleClass(options.selectedClass, target.checked);
$(actionCheckboxes).each(function() {
if ($.data(this) === $.data(lastChecked) || $.data(this) === $.data(target)) {
inrange = (inrange) ? false : true;
}
if (inrange) {
$(this).prop("checked", target.checked)
.parent().parent().toggleClass(options.selectedClass, target.checked);
}
});
}
$(target).parent().parent().toggleClass(options.selectedClass, target.checked);
lastChecked = target;
updateCounter();
});
$('form#changelist-form table#result_list tr').on('change', 'td:gt(0) :input', function() {
list_editable_changed = true;
});
$('form#changelist-form button[name="index"]').on('click', function(event) {
if (list_editable_changed) {
return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
}
});
$('form#changelist-form input[name="_save"]').on('click', function(event) {
var action_changed = false;
$('select option:selected', options.actionContainer).each(function() {
if ($(this).val()) {
action_changed = true;
}
});
if (action_changed) {
if (list_editable_changed) {
return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action."));
} else {
return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."));
}
}
});
};
/* Setup plugin defaults */
$.fn.actions.defaults = {
actionContainer: "div.actions",
counterContainer: "span.action-counter",
allContainer: "div.actions span.all",
acrossInput: "div.actions input.select-across",
acrossQuestions: "div.actions span.question",
acrossClears: "div.actions span.clear",
allToggle: "#action-toggle",
selectedClass: "selected"
};
$(document).ready(function() {
var $actionsEls = $('tr input.action-select');
if ($actionsEls.length > 0) {
$actionsEls.actions();
}
});
})(django.jQuery);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment