Index: django/db/models/manager.py
===================================================================
--- django/db/models/manager.py	(revision 3145)
+++ django/db/models/manager.py	(working copy)
@@ -99,6 +99,9 @@
     def values(self, *args, **kwargs):
         return self.get_query_set().values(*args, **kwargs)
 
+    def search(self, *args, **kwargs):
+        return self.get_query_set().search(*args, **kwargs)
+
 class ManagerDescriptor(object):
     # This class ensures managers aren't accessible via model instances.
     # For example, Poll.objects works, but poll_obj.objects raises AttributeError.
Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py	(revision 3145)
+++ django/db/models/options.py	(working copy)
@@ -13,7 +13,7 @@
 
 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
                  'unique_together', 'permissions', 'get_latest_by',
-                 'order_with_respect_to', 'app_label')
+                 'order_with_respect_to', 'app_label', 'search_fields')
 
 class Options(object):
     def __init__(self, meta):
@@ -22,6 +22,7 @@
         self.verbose_name_plural = None
         self.db_table = ''
         self.ordering = []
+        self.search_fields = []
         self.unique_together =  []
         self.permissions =  []
         self.object_name, self.app_label = None, None
@@ -197,7 +198,7 @@
 
 class AdminOptions(object):
     def __init__(self, fields=None, js=None, list_display=None, list_filter=None,
-        date_hierarchy=None, save_as=False, ordering=None, search_fields=None,
+        date_hierarchy=None, save_as=False, ordering=None,
         save_on_top=False, list_select_related=False, manager=None, list_per_page=100):
         self.fields = fields
         self.js = js or []
@@ -205,7 +206,6 @@
         self.list_filter = list_filter or []
         self.date_hierarchy = date_hierarchy
         self.save_as, self.ordering = save_as, ordering
-        self.search_fields = search_fields or []
         self.save_on_top = save_on_top
         self.list_select_related = list_select_related
         self.list_per_page = list_per_page
Index: django/db/models/query.py
===================================================================
--- django/db/models/query.py	(revision 3145)
+++ django/db/models/query.py	(working copy)
@@ -233,6 +233,27 @@
                 "Cannot change a query once a slice has been taken."
         return self._clone(_limit=1, _order_by=('-'+latest_by,)).get()
 
+    def search(self, query, field_names=None):
+        """
+        Return all objects who has any the words in the given query
+        in any of it's fields given in the model's 'search_fields'
+        option or in the optional argument field_names.
+        """
+        search_fields = field_names or self.model._meta.search_fields
+        assert bool(search_fields), "search_fields() requires either a field_names parameter or 'search_fields' in the model"
+        assert self._limit is None and self._offset is None, \
+                "Cannot change a query once a slice has been taken."
+
+        or_queries = []
+        for bit in query.split():
+            for field_name in search_fields:
+                or_queries.append(Q(**{'%s__icontains' % field_name: bit}))
+
+        if or_queries:
+            return self._clone().filter(reduce(operator.or_, or_queries))
+        else:
+            return self
+
     def in_bulk(self, id_list):
         """
         Returns a dictionary mapping each of the given IDs to the object with
Index: django/core/management.py
===================================================================
--- django/core/management.py	(revision 3145)
+++ django/core/management.py	(working copy)
@@ -930,6 +930,14 @@
                 except models.FieldDoesNotExist:
                     e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
 
+        # Check search_fields attribute.
+        if opts.search_fields:
+            for field_name in opts.search_fields:
+                try:
+                    opts.get_field(field_name, many_to_many=False)
+                except models.FieldDoesNotExist:
+                    e.add(opts, '"search_fields" refers to "%s", a field that doesn\'t exist.' % field_name)
+
         # Check core=True, if needed.
         for related in opts.get_followed_related_objects():
             try:
Index: django/views/generic/list_detail.py
===================================================================
--- django/views/generic/list_detail.py	(revision 3145)
+++ django/views/generic/list_detail.py	(working copy)
@@ -7,7 +7,7 @@
 def object_list(request, queryset, paginate_by=None, page=None,
         allow_empty=False, template_name=None, template_loader=loader,
         extra_context=None, context_processors=None, template_object_name='object',
-        mimetype=None):
+        mimetype=None, allow_search=True):
     """
     Generic list of objects.
 
@@ -33,9 +33,16 @@
             number of pages, total
         hits
             number of objects, total
+        query
+            the search query string
     """
     if extra_context is None: extra_context = {}
     queryset = queryset._clone()
+
+    query = request.GET.get('query', '')
+    if searchable and queryset.model._meta.search_fields and query:
+        queryset = queryset.search(query)
+
     if paginate_by:
         paginator = ObjectPaginator(queryset, paginate_by)
         if not page:
@@ -59,11 +66,13 @@
             'previous': page - 1,
             'pages': paginator.pages,
             'hits' : paginator.hits,
+            'query': query,
         }, context_processors)
     else:
         c = RequestContext(request, {
             '%s_list' % template_object_name: queryset,
-            'is_paginated': False
+            'is_paginated': False,
+            'query': query,
         }, context_processors)
         if not allow_empty and len(queryset) == 0:
             raise Http404
Index: django/contrib/auth/models.py
===================================================================
--- django/contrib/auth/models.py	(revision 3145)
+++ django/contrib/auth/models.py	(working copy)
@@ -29,9 +29,11 @@
         verbose_name = _('group')
         verbose_name_plural = _('groups')
         ordering = ('name',)
-    class Admin:
         search_fields = ('name',)
 
+    class Admin:
+        pass
+
     def __str__(self):
         return self.name
 
@@ -70,6 +72,7 @@
         verbose_name = _('user')
         verbose_name_plural = _('users')
         ordering = ('username',)
+        search_fields = ('username', 'first_name', 'last_name', 'email')
     class Admin:
         fields = (
             (None, {'fields': ('username', 'password')}),
@@ -80,7 +83,6 @@
         )
         list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
         list_filter = ('is_staff', 'is_superuser')
-        search_fields = ('username', 'first_name', 'last_name', 'email')
 
     def __str__(self):
         return self.username
Index: django/contrib/redirects/models.py
===================================================================
--- django/contrib/redirects/models.py	(revision 3145)
+++ django/contrib/redirects/models.py	(working copy)
@@ -15,10 +15,10 @@
         db_table = 'django_redirect'
         unique_together=(('site', 'old_path'),)
         ordering = ('old_path',)
+        search_fields = ('old_path', 'new_path')
 
     class Admin:
         list_filter = ('site',)
-        search_fields = ('old_path', 'new_path')
 
     def __str__(self):
         return "%s ---> %s" % (self.old_path, self.new_path)
Index: django/contrib/comments/models.py
===================================================================
--- django/contrib/comments/models.py	(revision 3145)
+++ django/contrib/comments/models.py	(working copy)
@@ -90,6 +90,7 @@
         verbose_name = _('comment')
         verbose_name_plural = _('comments')
         ordering = ('-submit_date',)
+        search_fields = ('comment', 'user__username')
     class Admin:
         fields = (
             (None, {'fields': ('content_type', 'object_id', 'site')}),
@@ -100,7 +101,6 @@
         list_display = ('user', 'submit_date', 'content_type', 'get_content_object')
         list_filter = ('submit_date',)
         date_hierarchy = 'submit_date'
-        search_fields = ('comment', 'user__username')
 
     def __repr__(self):
         return "%s: %s..." % (self.user.username, self.comment[:100])
@@ -176,6 +176,7 @@
         verbose_name = _('free comment')
         verbose_name_plural = _('free comments')
         ordering = ('-submit_date',)
+        search_fields = ('comment', 'person_name')
     class Admin:
         fields = (
             (None, {'fields': ('content_type', 'object_id', 'site')}),
@@ -185,7 +186,6 @@
         list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object')
         list_filter = ('submit_date',)
         date_hierarchy = 'submit_date'
-        search_fields = ('comment', 'person_name')
 
     def __repr__(self):
         return "%s: %s..." % (self.person_name, self.comment[:100])
Index: django/contrib/flatpages/models.py
===================================================================
--- django/contrib/flatpages/models.py	(revision 3145)
+++ django/contrib/flatpages/models.py	(working copy)
@@ -18,13 +18,13 @@
         verbose_name = _('flat page')
         verbose_name_plural = _('flat pages')
         ordering = ('url',)
+        search_fields = ('url', 'title')
     class Admin:
         fields = (
             (None, {'fields': ('url', 'title', 'content', 'sites')}),
             ('Advanced options', {'classes': 'collapse', 'fields': ('enable_comments', 'registration_required', 'template_name')}),
         )
         list_filter = ('sites',)
-        search_fields = ('url', 'title')
 
     def __str__(self):
         return "%s -- %s" % (self.url, self.title)
Index: django/contrib/sites/models.py
===================================================================
--- django/contrib/sites/models.py	(revision 3145)
+++ django/contrib/sites/models.py	(working copy)
@@ -15,9 +15,9 @@
         verbose_name = _('site')
         verbose_name_plural = _('sites')
         ordering = ('domain',)
+        search_fields = ('domain', 'name')
     class Admin:
         list_display = ('domain', 'name')
-        search_fields = ('domain', 'name')
 
     def __str__(self):
         return self.domain
Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 3145)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -710,12 +710,8 @@
         qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field)
 
         # Apply keyword searches.
-        if self.lookup_opts.admin.search_fields and self.query:
-            for bit in self.query.split():
-                or_queries = [models.Q(**{'%s__icontains' % field_name: bit}) for field_name in self.lookup_opts.admin.search_fields]
-                other_qs = QuerySet(self.model)
-                other_qs = other_qs.filter(reduce(operator.or_, or_queries))
-                qs = qs & other_qs
+        if self.opts.search_fields and self.query:
+            qs = qs.search(self.query)
 
         if self.opts.one_to_one_field:
             qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to)
Index: django/contrib/admin/templates/admin/search_form.html
===================================================================
--- django/contrib/admin/templates/admin/search_form.html	(revision 3145)
+++ django/contrib/admin/templates/admin/search_form.html	(working copy)
@@ -1,6 +1,6 @@
 {% load adminmedia %}
 {% load i18n %}
-{% if cl.lookup_opts.admin.search_fields %}
+{% if cl.opts.search_fields %}
 <div id="toolbar"><form id="changelist-search" action="" method="get">
 <div><!-- DIV needed for valid HTML -->
 <label for="searchbar"><img src="{% admin_media_prefix %}img/admin/icon_searchbox.png" alt="Search" /></label>
Index: docs/generic_views.txt
===================================================================
--- docs/generic_views.txt	(revision 3145)
+++ docs/generic_views.txt	(working copy)
@@ -673,6 +673,10 @@
     * ``mimetype``: The MIME type to use for the resulting document. Defaults
       to the value of the ``DEFAULT_MIME_TYPE`` setting.
 
+    * ``allow_search``: A boolean specifying whether searching is allowed
+      for theese objects. If this is ``False``, objects will not be
+      searchable. By default, this is ``True``.
+
 **Template name:**
 
 If ``template_name`` isn't specified, this view will use the template
@@ -713,6 +717,9 @@
     * ``hits``: The total number of objects across *all* pages, not just this
       page.
 
+    * ``query``: The search query string. if searching is disabled,
+      this is an empty string.
+
 Notes on pagination
 ~~~~~~~~~~~~~~~~~~~
 
