=== added file 'django/contrib/admin/decorators.py'
--- django/contrib/admin/decorators.py	1970-01-01 00:00:00 +0000
+++ django/contrib/admin/decorators.py	2008-03-01 07:08:42 +0000
@@ -0,0 +1,5 @@
+def admin_action( **kwargs ):
+    def _deco( f ):
+        f.admin_action = kwargs
+        return f
+    return _deco

=== added file 'django/contrib/admin/templates/admin/bulk_actions.html'
--- django/contrib/admin/templates/admin/bulk_actions.html	1970-01-01 00:00:00 +0000
+++ django/contrib/admin/templates/admin/bulk_actions.html	2008-03-01 07:08:42 +0000
@@ -0,0 +1,4 @@
+{% if bulk_action_list %}
+	<select name="bulk_action">{% for ba in bulk_action_list.items %}<option value="{{ ba.0 }}">{{ba.1}}</option>{% endfor %}</select>
+{% if bulk_action_list %}<input type="submit" >{% endif %}
+{% endif %}

=== added file 'django/contrib/admin/templates/admin/bulk_list.html'
--- django/contrib/admin/templates/admin/bulk_list.html	1970-01-01 00:00:00 +0000
+++ django/contrib/admin/templates/admin/bulk_list.html	2008-03-01 07:08:42 +0000
@@ -0,0 +1,16 @@
+{% extends "admin/base_site.html" %}
+{% load adminmedia admin_list i18n %}
+{% block stylesheet %}{% admin_media_prefix %}css/changelists.css{% endblock %}
+{% block bodyclass %}change-list{% endblock %}
+{% block userlinks %}<a href="../../doc/">{% trans 'Documentation' %}</a> / <a href="../../password_change/">{% trans 'Change password' %}</a> / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
+{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../../../{{app_label}}/{{model_name}}">{{model_name}}</a></div>{% endblock %}{% endif %}
+{% block coltype %}flex{% endblock %}
+{% block content %}
+<div id="content-main">
+<div class="module" id="changelist">
+bulk operation : {{action }}<br/>
+on items: {{ id_list }}<br/>
+outcome: {{ message }}<br/>
+</div>
+</div>
+{% endblock %}

=== modified file 'django/contrib/admin/templates/admin/change_form.html'
--- django/contrib/admin/templates/admin/change_form.html	2008-03-01 06:25:23 +0000
+++ django/contrib/admin/templates/admin/change_form.html	2008-03-01 07:08:42 +0000
@@ -56,6 +56,17 @@
 {% endif %}
 {% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
 {% block after_related_objects %}{% endblock %}
+{% if model_actions %}
+<fieldset class="module aligned ()">
+    <h2>SpecialOps</h2>
+    There are some specialized actions that you may also perform on this object:
+    <ul>
+    {% for action in model_actions.items %}
+        <li><a href="action/{{action.0}}/">{{ action.1 }}</a></li>
+    {% endfor %}
+    </ul>
+</fieldset>
+{% endif %}
 {% submit_row %}
 {% if add %}
    <script type="text/javascript">document.getElementById("{{ first_form_field_id }}").focus();</script>

=== modified file 'django/contrib/admin/templates/admin/change_list.html'
--- django/contrib/admin/templates/admin/change_list.html	2008-03-01 06:25:23 +0000
+++ django/contrib/admin/templates/admin/change_list.html	2008-03-01 07:08:42 +0000
@@ -16,7 +16,10 @@
 {% block search %}{% search_form cl %}{% endblock %}
 {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
 {% block filters %}{% filters cl %}{% endblock %}
+<form action="bulk/" method="POST">
 {% block result_list %}{% result_list cl %}{% endblock %}
+{% block bulk_actions %}{% bulk_actions cl %}{% endblock %}
+</form>
 {% block pagination %}{% pagination cl %}{% endblock %}
 </div>
 </div>

=== modified file 'django/contrib/admin/templates/admin/change_list_results.html'
--- django/contrib/admin/templates/admin/change_list_results.html	2008-03-01 06:25:23 +0000
+++ django/contrib/admin/templates/admin/change_list_results.html	2008-03-01 07:08:42 +0000
@@ -2,6 +2,7 @@
 <table cellspacing="0">
 <thead>
 <tr>
+<th><a href="#all">+</a>/<a href="#none">-</a></th>
 {% for header in result_headers %}<th{{ header.class_attrib }}>
 {% if header.sortable %}<a href="{{ header.url }}">{% endif %}
 {{ header.text|capfirst }}

=== modified file 'django/contrib/admin/templatetags/admin_list.py'
--- django/contrib/admin/templatetags/admin_list.py	2008-03-01 06:25:23 +0000
+++ django/contrib/admin/templatetags/admin_list.py	2008-03-01 07:08:42 +0000
@@ -186,8 +186,8 @@
             first = False
             url = cl.url_for_result(result)
             result_id = str(getattr(result, pk)) # str() is needed in case of 23L (long ints)
-            yield ('<%s%s><a href="%s"%s>%s</a></%s>' % \
-                (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr, table_tag))
+            yield ('<%s%s><input type="checkbox" name="pk" value="%s" ></%s><%s%s><a href="%s"%s>%s</a></%s>' % \
+                (table_tag, row_class, result_id, table_tag, table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr, table_tag))
         else:
             yield ('<td%s>%s</td>' % (row_class, result_repr))
 
@@ -198,9 +198,34 @@
 def result_list(cl):
     return {'cl': cl,
             'result_headers': list(result_headers(cl)),
-            'results': list(results(cl))}
+            'results': list(results(cl)),
+            #'bulk_action_list': bulk_actions( cl )['bulk_action_list'], # not really needed
+    }
 result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
 
+
+def bulk_actions( cl ):
+    """ This inspects the models for the bulk_action member.
+    """
+    from django.db import models
+    print cl.model
+    badict = dict()
+    for m in dir( cl.model ): # find the manager
+        # content_type has some sort of voodoo that precludes calls to getattr
+        try:
+            a = getattr( cl.model, m )
+            if isinstance( a, ( models.Manager ) ):
+                for n in dir( a ): # find the bulk_actions
+                    b = getattr( a, n )
+                    if hasattr( b, 'admin_action' ):
+                        badict[n] = b.admin_action.get( 'name', n )
+        except AttributeError, ae:
+            print ae
+
+    return { 'bulk_action_list': badict, }
+bulk_actions = register.inclusion_tag("admin/bulk_actions.html")(bulk_actions)
+
+
 def date_hierarchy(cl):
     if cl.lookup_opts.admin.date_hierarchy:
         field_name = cl.lookup_opts.admin.date_hierarchy

=== modified file 'django/contrib/admin/urls.py'
--- django/contrib/admin/urls.py	2008-03-01 06:25:23 +0000
+++ django/contrib/admin/urls.py	2008-03-01 07:08:42 +0000
@@ -35,6 +35,8 @@
     # Add/change/delete/history
     ('^([^/]+)/([^/]+)/$', 'django.contrib.admin.views.main.change_list'),
     ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
+    ('^([^/]+)/([^/]+)/bulk/$', 'django.contrib.admin.views.main.bulk_action'),
+    ('^([^/]+)/([^/]+)/([^/]+)/action/(.+)/$', 'django.contrib.admin.views.main.object_action'),
     ('^([^/]+)/([^/]+)/(.+)/history/$', 'django.contrib.admin.views.main.history'),
     ('^([^/]+)/([^/]+)/(.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'),
     ('^([^/]+)/([^/]+)/(.+)/$', 'django.contrib.admin.views.main.change_stage'),

=== modified file 'django/contrib/admin/views/main.py'
--- django/contrib/admin/views/main.py	2008-03-01 06:25:23 +0000
+++ django/contrib/admin/views/main.py	2008-03-01 07:08:42 +0000
@@ -298,6 +298,18 @@
     return render_change_form(model, manipulator, c, add=True)
 add_stage = staff_member_required(never_cache(add_stage))
 
+def get_actions_from_model( mdl ):
+    """ get the specialops admin actions from a model
+    """
+    print "inspecting ", mdl
+    actions = dict() # list() 
+    for a in dir( mdl ):
+        m = getattr( mdl, a, '' )
+        if hasattr( m, 'admin_action' ):
+            actions[a] = m.admin_action.get('name',a)  #actions.append( a )
+    print actions
+    return actions
+
 def change_stage(request, app_label, model_name, object_id):
     model = models.get_model(app_label, model_name)
     object_id = unquote(object_id)
@@ -393,10 +405,77 @@
         'object_id': object_id,
         'original': manipulator.original_object,
         'is_popup': request.REQUEST.has_key('_popup'),
+        'model_actions': get_actions_from_model( model ),
     })
     return render_change_form(model, manipulator, c, change=True)
 change_stage = staff_member_required(never_cache(change_stage))
 
+def bulk_action( request, app_label, model_name ):
+    """called when the list of items is submitted
+    """
+    model = models.get_model(app_label, model_name)
+    if request.POST:
+        new_data = request.POST.copy()
+        action = new_data['bulk_action']
+        pks = new_data.getlist('pk')
+        res = 'NO ACTION TAKEN'
+
+        # find the func 
+        func = None
+        for member_name in dir( model ):
+            member = getattr( model, member_name, "" )
+            if isinstance( member,  models.Manager  ) :
+                try:
+                    func = getattr( member, action )
+                except AttributeError, as:
+                    pass # not the right manager, or bogus action
+        if func:
+            id_list = [ int( x ) for x in pks ]
+            res = func( id_list )
+            
+        
+        c = template.RequestContext(request, {
+            'title': "%s : %s bulk operation -- %s" % ( app_label, model_name, func.admin_action.get( 'name', action) ),
+            'app_label': app_label,
+            'model_name': model_name,
+            'id_list': pks,
+            'action': action,
+            'message': res,
+        })
+        return render_to_response([ 'admin/%s/bulk_list.html' % app_label,
+                               'admin/bulk_list.html'], context_instance=c)
+
+    return HttpResponse( "yo" )
+bulk_action = staff_member_required( never_cache( bulk_action ) )
+
+def object_action( request, app_label, model_name, object_id, action ):
+    """called when the user presses an object_action button on the change page.
+       the POSTed data should include model_action=myfunc&pk=123
+    """
+    pass
+    model = models.get_model(app_label, model_name)
+    #if request.POST:
+    res = 'NO ACTION TAKEN'
+    obj = model.objects.get( pk = int( object_id ) )
+    print obj
+    print dir( obj )
+    func = getattr( obj, action )
+    print func
+    res = func()
+    c = template.RequestContext(request, {
+            'title': "%s : %s %s operation" % ( app_label, model_name ,action ),
+            'app_label': app_label,
+            'model_name': model_name,
+            'id_list': ( object_id, ),
+            'action': action,
+            'message': res,
+    })
+    return render_to_response([ 'admin/%s/bulk_list.html' % app_label,
+                               'admin/bulk_list.html'], context_instance=c)
+    return HttpResponse( "yo" )
+object_action = staff_member_required( never_cache( object_action ) )
+
+
 def _nest_help(obj, depth, val):
     current = obj
     for i in range(depth):
@@ -777,3 +856,4 @@
                                'admin/%s/change_list.html' % app_label,
                                'admin/change_list.html'], context_instance=c)
 change_list = staff_member_required(never_cache(change_list))
+

=== modified file 'django/contrib/comments/models.py'
--- django/contrib/comments/models.py	2008-03-01 06:25:23 +0000
+++ django/contrib/comments/models.py	2008-03-01 07:08:42 +0000
@@ -160,6 +160,31 @@
             {'user': self.user.username, 'date': self.submit_date,
             'comment': self.comment, 'domain': self.site.domain, 'url': self.get_absolute_url()}
 
+from django.contrib.admin.decorators import *
+
+class FreeCommentMgr( models.Manager ):
+    @admin_action( name='Ban user permanently', prompt='killl killl killllll', advice='yeah' )
+    def burninate( self, id_list ):
+        print "\n".join( dir( self ) )
+        print "slaughtering ", id_list
+        res = "killing....\n"
+        for id in id_list:
+            obj = self.get( pk=id )
+            res += repr( obj ) + "\n"
+        return "killing %s" % res
+
+    @admin_action( name='Delete selected posts' )
+    def slaughter( self, id_list ):
+        print "slaughtering ", id_list
+        res = ""
+        for id in id_list:
+            obj = self.get( pk=id )
+            res += repr( obj ) + "<br/>\n"
+            #obj.delete()
+        return "Deleted %s" % res
+
+
+
 class FreeComment(models.Model):
     # A FreeComment is a comment by a non-registered user.
     content_type = models.ForeignKey(ContentType)
@@ -172,6 +197,7 @@
     # TODO: Change this to is_removed, like Comment
     approved = models.BooleanField(_('approved by staff'))
     site = models.ForeignKey(Site)
+    objects = FreeCommentMgr()
     class Meta:
         verbose_name = _('free comment')
         verbose_name_plural = _('free comments')
@@ -183,9 +209,14 @@
             ('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
         )
         list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object')
-        list_filter = ('submit_date',)
+        list_filter = ('submit_date','is_public', 'approved')
         date_hierarchy = 'submit_date'
         search_fields = ('comment', 'person_name')
+        
+    @admin_action( name='Send a warning message to this user' )
+    def harass( self ):
+        return "warning sent to  %s" % ( self.person_name, )
+
 
     def __repr__(self):
         return "%s: %s..." % (self.person_name, self.comment[:100])
@@ -206,6 +237,7 @@
 
     get_content_object.short_description = _('Content object')
 
+
 class KarmaScoreManager(models.Manager):
     def vote(self, user_id, comment_id, score):
         try:

