Index: django/views/defaults.py
===================================================================
--- django/views/defaults.py	(revision 3900)
+++ django/views/defaults.py	(working copy)
@@ -24,33 +24,12 @@
     if absurl.startswith('http://'):
         return http.HttpResponseRedirect(absurl)
 
+    # Try to figure out the related site for the object
     object_domain = None
+    site = Site.objects.get_for_obj(obj)
+    if site:
+        object_domain = site.domain
 
-    # Otherwise, we need to introspect the object's relationships for a
-    # relation to the Site object
-    opts = obj._meta
-
-    # First, look for an many-to-many relationship to sites
-    for field in opts.many_to_many:
-        if field.rel.to is Site:
-            try:
-                object_domain = getattr(obj, field.name).all()[0].domain
-            except IndexError:
-                pass
-            if object_domain is not None:
-                break
-
-    # Next look for a many-to-one relationship to site
-    if object_domain is None:
-        for field in obj._meta.fields:
-            if field.rel and field.rel.to is Site:
-                try:
-                    object_domain = getattr(obj, field.name).domain
-                except Site.DoesNotExist:
-                    pass
-                if object_domain is not None:
-                    break
-
     # Fall back to the current site (if possible)
     if object_domain is None:
         try:
Index: django/contrib/sites/models.py
===================================================================
--- django/contrib/sites/models.py	(revision 3900)
+++ django/contrib/sites/models.py	(working copy)
@@ -2,10 +2,35 @@
 from django.utils.translation import gettext_lazy as _
 
 class SiteManager(models.Manager):
+
     def get_current(self):
         from django.conf import settings
         return self.get(pk=settings.SITE_ID)
 
+    def get_for_object(self, obj):
+        """
+        Returns the Site object for the given model object, if that object has
+        a corresponding relation. Otherwise it returns None.
+        """
+        opts = obj._meta
+
+        # First, look for an many-to-many relationship to sites
+        for field in opts.many_to_many:
+            if field.rel.to is Site:
+                if getattr(obj, field.name):
+                    try:
+                        return getattr(obj, field.name).all()[0]
+                    except IndexError:
+                        pass
+
+        # Next look for a many-to-one relationship to sites
+        for field in obj._meta.fields:
+            if field.rel and field.rel.to is Site:
+                return getattr(obj, field.name)
+
+        # Cannot determine a direct relation to sites, so return None
+        return None
+
 class Site(models.Model):
     domain = models.CharField(_('domain name'), maxlength=100)
     name = models.CharField(_('display name'), maxlength=50)
Index: django/contrib/admin/templatetags/log.py
===================================================================
--- django/contrib/admin/templatetags/log.py	(revision 3900)
+++ django/contrib/admin/templatetags/log.py	(working copy)
@@ -1,11 +1,16 @@
 from django import template
+from django.conf import settings
 from django.contrib.admin.models import LogEntry
+from django.db import models
 
 register = template.Library()
 
 class AdminLogNode(template.Node):
-    def __init__(self, limit, varname, user):
-        self.limit, self.varname, self.user = limit, varname, user
+    def __init__(self, limit, varname, user, site):
+        self.limit = limit
+        self.varname = varname
+        self.user = user
+        self.site = site
 
     def __repr__(self):
         return "<GetAdminLog Node>"
@@ -13,7 +18,15 @@
     def render(self, context):
         if self.user is not None and not self.user.isdigit():
             self.user = context[self.user].id
-        context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit]
+
+        entries = LogEntry.objects.filter(user__id__exact=self.user)
+        if self.site:
+            if not self.site.isdigit():
+                self.site = context[self.site].id
+            entries.filter(models.Q(site_id=self.site) |
+                           models.Q(site_id__isnull=True))
+
+        context[self.varname] = entries.select_related()[:self.limit]
         return ''
 
 class DoGetAdminLog:
@@ -23,16 +36,24 @@
     Usage::
 
         {% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %}
+        
+    or::
 
+        {% get_admin_log [limit] as [varname] for_site [context_var_containing_site_obj] %}
+
     Examples::
 
         {% get_admin_log 10 as admin_log for_user 23 %}
-        {% get_admin_log 10 as admin_log for_user user %}
+        {% get_admin_log 10 as admin_log for_user varname %}
+        {% get_admin_log 10 as admin_log for_site %}
+        {% get_admin_log 10 as admin_log for_site 2 %}
+        {% get_admin_log 10 as admin_log for_site varname %}
         {% get_admin_log 10 as admin_log %}
 
     Note that ``context_var_containing_user_obj`` can be a hard-coded integer
     (user ID) or the name of a template context variable containing the user
-    object whose ID you want.
+    object whose ID you want. The ``context_var_containing_site_obj`` works the
+    same way, except that when you omit a value, it uses the current site.
     """
     def __init__(self, tag_name):
         self.tag_name = tag_name
@@ -45,9 +66,14 @@
             raise template.TemplateSyntaxError, "First argument in '%s' must be an integer" % self.tag_name
         if tokens[2] != 'as':
             raise template.TemplateSyntaxError, "Second argument in '%s' must be 'as'" % self.tag_name
+        user = site = None
         if len(tokens) > 4:
-            if tokens[4] != 'for_user':
-                raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name
-        return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None))
+            if tokens[4] not in ('for_user', 'for_site'):
+                raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user' or 'for_site'" % self.tag_name
+            if tokens[4] == 'for_user':
+                user = len(tokens) > 5 and tokens[5] or None
+            else:
+                site = len(tokens) > 5 and tokens[5] or str(settings.SITE_ID)
+        return AdminLogNode(limit=tokens[1], varname=tokens[3], user=user, site=site)
 
 register.tag('get_admin_log', DoGetAdminLog('get_admin_log'))
Index: django/contrib/admin/models.py
===================================================================
--- django/contrib/admin/models.py	(revision 3900)
+++ django/contrib/admin/models.py	(working copy)
@@ -1,6 +1,7 @@
 from django.db import models
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
 from django.utils.translation import gettext_lazy as _
 
 ADDITION = 1
@@ -8,13 +9,20 @@
 DELETION = 3
 
 class LogEntryManager(models.Manager):
-    def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
-        e = self.model(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message)
+    def log_action(self, user, model, obj, action, message=''):
+        site_id = None
+        site = Site.objects.get_for_object(obj)
+        if site:
+            site_id = site.id
+        e = self.model(None, None, user.id, site_id,
+                       ContentType.objects.get_for_model(model).id,
+                       obj._get_pk_val(), str(obj)[:200], action, message)
         e.save()
 
 class LogEntry(models.Model):
     action_time = models.DateTimeField(_('action time'), auto_now=True)
     user = models.ForeignKey(User)
+    site = models.ForeignKey(Site, blank=True, null=True)
     content_type = models.ForeignKey(ContentType, blank=True, null=True)
     object_id = models.TextField(_('object id'), blank=True, null=True)
     object_repr = models.CharField(_('object repr'), maxlength=200)
Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 3900)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -253,7 +253,7 @@
         if not errors:
             new_object = manipulator.save(new_data)
             pk_value = new_object._get_pk_val()
-            LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), ADDITION)
+            LogEntry.objects.log_action(request.user, model, new_object, ADDITION)
             msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
             # Here, we distinguish between different save types by checking for
             # the presence of keys in request.POST.
@@ -340,7 +340,7 @@
             change_message = ' '.join(change_message)
             if not change_message:
                 change_message = _('No fields changed.')
-            LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), CHANGE, change_message)
+            LogEntry.objects.log_action(request.user, model, new_object, CHANGE, change_message)
 
             msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
             if request.POST.has_key("_continue"):
@@ -507,7 +507,7 @@
             raise PermissionDenied
         obj_display = str(obj)
         obj.delete()
-        LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, object_id, obj_display, DELETION)
+        LogEntry.objects.log_action(request.user, model, obj, DELETION)
         request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': opts.verbose_name, 'obj': obj_display})
         return HttpResponseRedirect("../../")
     extra_context = {
