Index: models/auth.py
===================================================================
--- models/auth.py	(revision 639)
+++ models/auth.py	(working copy)
@@ -222,3 +222,33 @@
     def _module_log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
         e = LogEntry(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
         e.save()
+
+class CsrfToken(meta.Model):
+    token = meta.CharField(maxlength=200)
+    issued = meta.DateTimeField(auto_now=True)
+    user = meta.ForeignKey(User)
+    class META:
+        module_name = 'csrf'
+        verbose_name_plural = 'CSRF tokens'
+        db_table = 'auth_csrf_tokens'
+        ordering = ('-issued',)
+
+    def __repr__(self):
+        return self.token
+
+    def _module_create_token(user_id):
+        import md5, random
+        token = md5.new(str(random.random())).hexdigest()
+        ct = CsrfToken(None, token, 
+            datetime.datetime.now(), user_id)
+        ct.save()
+        return ct
+
+    def _module_check_token(token, user_id):
+        try:
+            token = get_object(token__exact = token,
+                select = {'user_id': user_id})
+        except CsrfTokenDoesNotExist:
+            return False
+        token.delete()
+        return True
Index: conf/admin_templates/delete_confirmation_generic.html
===================================================================
--- conf/admin_templates/delete_confirmation_generic.html	(revision 639)
+++ conf/admin_templates/delete_confirmation_generic.html	(working copy)
@@ -2,6 +2,8 @@
 
 {% block content %}
 
+{% if csrf_failed %}<p class="errornote">CSRF protection: Please re-submit.</p>{% endif %}
+
 {% if perms_lacking %}
     <p>Deleting the {{ object_name }} "{{ object }}" would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:</p>
     <ul>
@@ -15,6 +17,7 @@
     <form action="" method="post">
     <input type="hidden" name="post" value="yes" />
     <input type="submit" value="Yes, I'm sure" />
+    {% load csrf %}{% csrf_token %}
     </form>
 {% endif %}
 
Index: templatetags/csrf.py
===================================================================
--- templatetags/csrf.py	(revision 0)
+++ templatetags/csrf.py	(revision 0)
@@ -0,0 +1,16 @@
+from django.core import template
+from django.models.auth import csrf
+import random, md5, datetime
+
+class CsrfTokenNode(template.Node):
+    def render(self, context):
+        token = csrf.create_token(context['user'].id)
+        return '<input type="hidden" name="csrf_token" value="%s">' % token.token
+
+def csrf_token(parser, token):
+    """
+    {% csrf_token %}
+    """
+    return CsrfTokenNode()
+
+template.register_tag('csrf_token', csrf_token)
Index: views/admin/main.py
===================================================================
--- views/admin/main.py	(revision 639)
+++ views/admin/main.py	(working copy)
@@ -3,7 +3,7 @@
 from django.core import formfields, meta, template_loader
 from django.core.exceptions import Http404, ObjectDoesNotExist, PermissionDenied
 from django.core.extensions import DjangoContext as Context
-from django.models.auth import log
+from django.models.auth import log, csrf
 from django.utils.html import strip_tags
 from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
 from django.utils.text import capfirst, get_text_list
@@ -588,6 +588,7 @@
     if opts.admin.save_on_top:
         t.extend(_get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects))
     t.append('{% if form.error_dict %}<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>{% endif %}\n')
+    t.append('{% if csrf_failed %}<p class="errornote">CSRF protection: Please re-submit.</p>{% endif %}\n')
     for fieldset_name, options in admin_field_objs:
         t.append('<fieldset class="module aligned %s">\n\n' % options.get('classes', ''))
         if fieldset_name:
@@ -687,6 +688,7 @@
         t.append('<li id="p{%% firstof %(x)s %%}"><span id="handlep{%% firstof %(x)s %%}">{{ object|truncatewords:"5" }}</span></li>' % \
             {'x': ' '.join(['object.%s' % o.pk.name for o in ordered_objects])})
         t.append('{% endfor %}</ul>{% endif %}\n')
+    t.append('{% load csrf %}{% csrf_token %}\n')
     t.append('</form>\n</div>\n{% endblock %}')
     return ''.join(t)
 
@@ -764,12 +766,15 @@
     if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
         raise PermissionDenied
     manipulator = mod.AddManipulator()
+    csrf_failed = False
     if request.POST:
         new_data = request.POST.copy()
         if opts.has_field_type(meta.FileField):
             new_data.update(request.FILES)
         errors = manipulator.get_validation_errors(new_data)
-        if not errors and not request.POST.has_key("_preview"):
+        # Check the csrf_token
+        csrf_failed = not csrf.check_token(request.POST.get('csrf_token', ''), request.user.id)
+        if not errors and not csrf_failed and not request.POST.has_key("_preview"):
             for f in opts.many_to_many:
                 if f.rel.raw_id_admin:
                     new_data.setlist(f.name, new_data[f.name].split(","))
@@ -844,6 +849,7 @@
         'title': 'Add %s' % opts.verbose_name,
         "form": form,
         "is_popup": request.REQUEST.has_key("_popup"),
+        "csrf_failed": csrf_failed,
     })
     if object_id_override is not None:
         c['object_id'] = object_id_override
@@ -864,13 +870,16 @@
         raise Http404
 
     inline_related_objects = opts.get_inline_related_objects()
+    csrf_failed = False
     if request.POST:
         new_data = request.POST.copy()
         if opts.has_field_type(meta.FileField):
             new_data.update(request.FILES)
 
         errors = manipulator.get_validation_errors(new_data)
-        if not errors and not request.POST.has_key("_preview"):
+        # Check the csrf_token
+        csrf_failed = not csrf.check_token(request.POST.get('csrf_token', ''), request.user.id)
+        if not errors and not csrf_failed and not request.POST.has_key("_preview"):
             for f in opts.many_to_many:
                 if f.rel.raw_id_admin:
                     new_data.setlist(f.name, new_data[f.name].split(","))
@@ -969,7 +978,8 @@
         "form": form,
         'object_id': object_id,
         'original': manipulator.original_object,
-        'is_popup' : request.REQUEST.has_key('_popup'),
+        'is_popup': request.REQUEST.has_key('_popup'),
+        'csrf_failed': csrf_failed,
     })
     raw_template = _get_template(opts, app_label, change=True)
 #     return HttpResponse(raw_template, mimetype='text/plain')
@@ -1073,8 +1083,10 @@
     deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags(repr(obj))), []]
     perms_needed = sets.Set()
     _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
-
-    if request.POST: # The user has already confirmed the deletion.
+    csrf_failed = False
+    if request.POST:
+        csrf_failed = not csrf.check_token(request.POST.get('csrf_token', ''), request.user.id)
+    if request.POST and not csrf_failed: # The user has already confirmed the deletion.
         if perms_needed:
             raise PermissionDenied
         obj_repr = repr(obj)
@@ -1089,6 +1101,7 @@
         "object": obj,
         "deleted_objects": deleted_objects,
         "perms_lacking": perms_needed,
+        "csrf_failed": csrf_failed,
     })
     return HttpResponse(t.render(c), mimetype='text/html; charset=utf-8')
 
