Index: django/views/generic/collections.py
===================================================================
--- django/views/generic/collections.py	(revision 0)
+++ django/views/generic/collections.py	(revision 0)
@@ -0,0 +1,201 @@
+from django.conf.urls.defaults import patterns, url
+from django.core.exceptions import ImproperlyConfigured
+from django.db.models import Model
+from django.db.models.query import QuerySet
+from django.views import generic
+
+class ViewCollection(object):
+    """
+        A base class based view collection
+    """
+    class_based_views = None
+    current_app = None
+
+    def __init__(self, *args, **kwargs):
+        """
+        Constructor.
+        """
+        for key, value in kwargs.iteritems():
+            setattr(self, key, value)
+
+    def get_urls(self):
+        """
+        Returns the urlspatterns to be used in for the views
+        """
+        urlpatterns = patterns('')
+
+        for name, regex, cbv in self.class_based_views:
+            urlpatterns += patterns('',
+                url(regex, cbv.as_view(), name=name)
+            )
+        return urlpatterns
+
+    @property
+    def urls(self):
+        """
+        Property to be used in the URLConf in an include method
+        """
+        return self.get_urls(), self.current_app
+
+    def get_urls_namespacestring(self):
+        """
+        Returns the urls prefix we should use for the url reversing
+        """
+        if self.current_app is not None:
+            return '%s:' % self.current_app
+        return ''
+
+
+class CrudViewCollectionViewMixin(object):
+    """
+        A mixin for class based views for the CrudViewCollection
+    """
+    extra_context = None
+    current_app = None
+
+    def get_context_data(self, **kwargs):
+        sup = super(CrudViewCollectionViewMixin, self)
+        context = {}
+        if hasattr(sup, 'get_context_data'):
+            context = sup.get_context_data(**kwargs)
+        if self.extra_context is not None:
+            context.update(self.extra_context)
+        return context
+
+    def render_to_response(self, context, **response_kwargs):
+        """
+        Returns a response with a template rendered with the given context.
+        """
+        if 'current_app' not in response_kwargs:
+            response_kwargs['current_app'] = self.current_app
+        return super(CrudViewCollectionViewMixin, self).render_to_response(
+                context,
+                **response_kwargs)
+
+
+class CrudListView(CrudViewCollectionViewMixin, generic.ListView):
+    pass
+
+
+class CrudCreateView(CrudViewCollectionViewMixin, generic.CreateView):
+    pass
+
+
+class CrudUpdateView(CrudViewCollectionViewMixin, generic.UpdateView):
+    pass
+
+
+class CrudDeleteView(CrudViewCollectionViewMixin, generic.DeleteView):
+    pass
+
+
+class CrudDetailView(CrudViewCollectionViewMixin, generic.DetailView):
+    pass
+
+
+class CrudViewCollection(ViewCollection):
+    """
+    A Create, Read (Detail/List), Update, Delete View Collection
+    """
+    templates = None
+    model = None
+    queryset = None
+
+    forms = None
+    templates = None
+
+
+    def __init__(self, *args, **kwargs):
+        """
+        Constructor
+        """
+        super(CrudViewCollection, self).__init__(*args, **kwargs)
+        self.init_views()
+
+        #Make sure we have a unique current_app if it is not yet set
+        if self.current_app is None:
+            self.current_app = 'crud_%s' % (self.get_model().__class__.lower())
+
+
+    def init_views(self):
+        """
+        Initialize the class based views
+        """
+        self.class_based_views = [
+            ('list', r'^$', CrudListView.as_view(
+                            model = self.get_model(),
+                            queryset = self.get_queryset(),
+                            template_name = self.get_template_for('list'),
+                            current_app = self.current_app
+            )),
+            ('create', r'^/create/$', CrudCreateView.as_view(
+                            model = self.get_model(),
+                            queryset = self.get_queryset(),
+                            form_class = self.get_form_for('create'),
+                            template_name = self.get_template_for('create'),
+                            current_app = self.current_app
+            )),
+            ('update', r'^/update/(?P<pk>\d+)/$', CrudUpdateView.as_view(
+                            model = self.get_model(),
+                            queryset = self.get_queryset(),
+                            form_class = self.get_form_for('update'),
+                            template_name = self.get_template_for('update'),
+                            current_app = self.current_app
+            )),
+            ('delete', r'^/delete/(?P<pk>\d+)/$', CrudDeleteView.as_view(
+                            model = self.get_model(),
+                            queryset = self.get_queryset(),
+                            template_name = self.get_template_for('delete'),
+                            success_url = '%s%s' % (
+                                self.get_urls_namespacestring(),
+                                'list'
+                            ),
+                            current_app = self.current_app,
+            )),
+            ('detail', r'^/detail/(?P<pk>\d+)/$', CrudDetailView.as_view(
+                            model = self.get_model(),
+                            queryset = self.get_queryset(),
+                            form_class = self.get_form_for('detail'),
+                            template_name = self.get_template_for('detail'),
+                            current_app = self.current_app
+            ))
+        ]
+
+    def get_template_for(self, view_name):
+        """
+        Should return a template path or None
+        """
+        if self.templates is None:
+            self.templates = {}
+        return self.templates.get(view_name)
+
+    def get_form_for(self, view_name):
+        """
+        Should return the form object or None
+        Note: If None the CBV will create a ModelForm
+        """
+        if self.forms is None:
+            self.forms = {}
+        return self.forms.get(view_name)
+
+    def get_model(self):
+        """
+        Return the model or get it from the queryset
+        """
+        if isinstance(self.model, Model):
+            return self.model
+        elif isinstance(self.queryset, QuerySet):
+            return self.queryset.model
+        raise ImproperlyConfigured(
+            'No model or queryset specified on %r' % self.__class__)
+
+    def get_queryset(self):
+        """
+        Return the queryset or get it from the model
+        """
+        if isinstance(self.queryset, QuerySet):
+            return self.queryset
+        elif isinstance(self.model, Model):
+            return self.model.objects.all()
+        raise ImproperlyConfigured(
+            'No model or queryset specified on %r' % self.__class__)
