1) enable customized Admin class
from django.contrib import admin
class QuestionAdmin(admin.ModelAdmin):
pass
admin.site.register(Question, QuestionAdmin)
2) customize fields to display at list page
class QuestionAdmin(admin.ModelAdmin):
# fields to display at list page
list_display = ('id', 'subject', )
# at list page, fields that when click will open individual edit page
# NOTE: when edit from list page, also call save_model()
# if save_model() depends on custom fields in custom form (which not exists in list page),
# one work around is to call super class method to handle model save
# see save_model() below.
list_display_links = ['question_html']
# fields to show at filter side bar
list_filter = ('status', 'subject', 'source', 'year')
# fields that can edit from list page
list_editable = ('solution', 'question_type', 'point', 'status', )
# - fields used for search when click search button
# - can search across relationship, e.g. subject__name
# - by default, search for objects contain case-insensitive specified keyword
# (i.e. in MySQL : WHERE source ILIKE '%Ent%')
# - for faster search performance
# + prefix field name with '=' to search exact match, e.g. WHERE year='2555'
# + prefix field name with '^' to match beginning of the field
search_fields = ['subject__name', 'source', '=year', 'tags__name', 'solution_html']
# specified fields that are read-only
# (enhance performance when display individual edit page)
readonly_fields = ['created_time', 'student', 'question']
# field used as date, and display as link in list page that when click can filter by that date field
date_hierarchy = 'created_time'
# specify default order when display list page
ordering = ['-id']
def save_model(self, request, obj, form, change):
# if not change from QuestionAdminForm, use default save_model()
if not isinstance(form, QuestionAdminForm):
super(QuestionAdmin,self).save_model(request, obj, form, change)
return
Some sample screens below.
2.1) display link in list page
by using a function in list_display, which return url and set allow_tags = True.
class ModelAdmin(admin.ModelAdmin):
list_display = ('id', 'view_link')
def view_link(self, obj):
q = Question.objects.get(node_id=obj.id)
return u'<a href="%s">%s</a>' % (q.get_absolute_url(),q.name)
view_link.allow_tags = True
2.2) filter data shown in list page
by overriding queryset() as shown below.
def queryset(self, request):
qs = super(NodeAdmin, self).queryset(request)
return qs.filter(type__in=[2, 3, 4])
2.3) collapse list filter sidebar (with jQuery)
detail here >> https://gist.github.com/abyx/1017597
class QuestionAdmin(admin.ModelAdmin):
list_filter = ['activity_flag']
class Media:
js = ['/static/js/list_filter_collapse.js' ] # path to JavaScript file
content of list_filter_collapse.js
(function($){
ListFilterCollapsePrototype = {
bindToggle: function(){
var that = this;
this.$filterTitle.click(function(){
that.$filterContent.slideToggle();
that.$list.toggleClass('filtered');
});
},
init: function(filterEl) {
this.$filterTitle = $(filterEl).children('h2');
this.$filterContent = $(filterEl).children('h3, ul');
$(this.$filterTitle).css('cursor', 'pointer');
this.$list = $('#changelist');
this.bindToggle();
}
}
function ListFilterCollapse(filterEl) {
this.init(filterEl);
}
ListFilterCollapse.prototype = ListFilterCollapsePrototype;
$(document).ready(function(){
$('#changelist-filter').each(function(){
var collapser = new ListFilterCollapse(this);
});
});
})(django.jQuery);
3) use inline to display/edit/add related object
# subclass from admin.TabularInline
class SectionResourceInline(admin.TabularInline):
# model used in this inline
model = SectionResource
# fields display in this inline
fields = ('type', 'seq_no', 'node')
# specify if to let user input id instead of select from drop-down list
# to improve performance
raw_id_fields = ['node']
# extra row displayed to add new data
extra = 1
4.1) use custom form to display/edit individual object
from django.contrib import admin
class QuestionAdminForm(forms.ModelForm):
...
class Meta:
model = Question
class QuestionAdmin(admin.ModelAdmin):
form = QuestionAdminForm
# group fields in custom Form
# 1st element in tuple = verbose name
# 2nd element in tuple = dict with grouped fields, each inner tuple are fields in same line
# e.g. group 'Information' has 2 lines,
# first line has field difficulty, second line has source, year, seq_no
# can specify custom fields together with fields in Model (e.g. new_chapter below)
fieldsets = (
('Content', {'fields': (('subject', 'chapter', 'new_chapter'), ), }),
('Information', {'fields': ('difficulty',
('source', 'year', 'seq_no'),
),
}),
)
...
Sample screen here.
4.2) keep original field value to use when save
in custom form : override __init__() to keep original field value somewhere.
class NodeAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(NodeAdminForm, self).__init__(*args, **kwargs)
# initiate values for custom fields if change_form
node = kwargs.pop('instance', None)
if node:
self.old_size = node.size
class Meta:
model = Node
in admin class : refer to original field value in form when save object
class NodeAdmin(admin.ModelAdmin):
form = NodeAdminForm
def save_model(self, request, obj, form, change):
if form.old_size != obj.size:
# do something
super(NodeAdmin, self).save_model(request, obj, form, change)
4.3) use custom fields in Form
class QuestionAdminForm(forms.ModelForm):
# queryset : to get available choices
# widget : use admin.widgets.FilteredSelectMultiple to display 2 boxes : available choices <--> selected choices
# 2nd parameter : True if display 2 boxes vertically, False if display horizontally
# rows in attrs is approximate size of widget?
internal_label = forms.ModelMultipleChoiceField(label='Internal Label', required=False,queryset=Label.objects.filter(type=Label.INTERNAL_LABEL_TYPE).order_by('name'),widget= admin.widgets.FilteredSelectMultiple('Internal Label', False, attrs={'rows':'10'})
# show as dropdown widget (single select)
# use ModelMultipleChoiceField for multiple select dropdown
chapter = forms.ModelChoiceField(queryset=Chapter.objects.all().order_by('name'), required=False)
def __init__(self, *args, **kwargs):
# to display other fields in model
super(QuestionAdminForm, self).__init__(*args, **kwargs)
# set required fields
self.fields['field_name'].required = True
...
# use RelatedFieldWidgetWrapper to add green PLUS sign besides FilteredSelectMultiple widget
# detail here >> http://dashdrum.com/blog/2012/07/relatedfieldwidgetwrapper/
# 1st parameter = widget to be wrapped
# 2nd parameter = a relation that defines the two models involved
# 3rd parameter = a reference to the admin site (assigned from Admin class' __init__())
# QuestionLabel is a class that has ForeignKey to both Question and Label
self.fields['internal_label'].widget =
admin.widgets.RelatedFieldWidgetWrapper(self.fields['internal_label'].widget,
QuestionLabel._meta.get_field('label').rel, self.admin_site)
class Meta:
# model related to Form
model = Question
# specify special widget for field
# AdminFileWidget is a widget to load file
widgets = {'question_html': admin.widgets.AdminFileWidget, }
class QuestionAdmin(admin.ModelAdmin):
form = QuestionAdminForm
def __init__(self, model, admin_site):
super(QuestionAdmin,self).__init__(model, admin_site)
# capture the admin_site
# for use with RelatedFieldWidgetWrapper in QuestionAdminForm
self.form.admin_site = admin_site
Sample screen here.
4.4) initiate default value in custom Form
class QuestionAdminForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
...
# get Model object from kwargs
question = kwargs.pop('instance', None)
if question:
# for simple fields, simply set value
self.question_html = question.question_html
# for single select choice field, simply set selected object id
self.initial['chapter'] = question.get_chapter_id()
# for multiple select choice field,
# create a dict with key = selected object id, value = True,
# then set as initial value
selected_internal_labels = {}
for r in Label.objects.filter(...):
selected_internal_labels[r.id] = True
self.initial['internal_label'] = selected_internal_labels
4.5) validate input data (before calling Admin class' save_model())
NOTE: method call sequence : ModelForm.clean() > Model.clean() > ModelAdmin.save_model() > Model.save()so validation can occur at ModelForm.clean() or Model.clean()
class QuestionAdminForm(forms.ModelForm):
...
def clean(self):
# default validation first
cleaned_data = super(QuestionAdminForm, self).clean()
# custom validation follows
# raise a ValidationError and error message will be displayed at top of edit Form
# ValidationError raised in Model.clean() also displayed in the same way
if some_condition:
raise ValidationError('subject mismatch')
# don't forget to return cleaned data
return cleaned_data
4.6) save model from custom Form
NOTE: method call sequence : ModelForm.clean() > Model.clean() > ModelAdmin.save_model() > Model.save()so setting default value can be done at ModelAdmin.save_model() or Model.save()
class QuestionAdmin(admin.ModelAdmin):
...
def save_model(self, request, obj, form, change):
# call super class' save_model() to handle changes from list page
# (if we use custom form with custom fields not in list page)
if not isinstance(form, QuestionAdminForm):
super(QuestionAdmin,self).save_model(request, obj, form, change)
return
# obj = model object to be saved
# can set default value from request
# NOTE: use request.POST.get() to avoid exception when key not exists (i.e. not input from Form)
# NOTE: when get a list from request object, use request.POST.getlist('a_key_to_list_value'),
# not simply call request.POST.get()
obj.created_by_id = request.user.id
# can set default value from form data
obj.question_html = form.question_html
# call super class' save_model() method for default process
super(QuestionAdmin,self).save_model(request, obj, form, change)
# then some special process follows
...
# call message_user() to display some messages
self.message_user(request, 'done...')
5) specific action in Admin Form
detail here >> https://docs.djangoproject.com/en/1.5/ref/contrib/admin/actions/
class QuestionAdmin(admin.ModelAdmin):
actions = ['my_action']
# queryset contain objects selected by user from list page
def my_action(self, request, queryset):
# for each selected object, do something
for r in queryset:
do_something(r)
# show some message after completed
self.message_user(request, "successfully do something")
# a verbose description to show in page
my_action.short_description = 'Do something for some purpose'
ไม่มีความคิดเห็น:
แสดงความคิดเห็น