[cig-commits] r11897 - cs/portal/trunk/northridge/SeismoWebPortal

leif at geodynamics.org leif at geodynamics.org
Thu May 1 20:14:00 PDT 2008


Author: leif
Date: 2008-05-01 20:14:00 -0700 (Thu, 01 May 2008)
New Revision: 11897

Added:
   cs/portal/trunk/northridge/SeismoWebPortal/mezzanine.py
Modified:
   cs/portal/trunk/northridge/SeismoWebPortal/urls.py
   cs/portal/trunk/northridge/SeismoWebPortal/views.py
Log:
Introduced the mezzanine layer, which will allow the replacement of
all CRUD views with a single function (see prototype implementation in
'root').  Initially, this module is simply a concatenation of Django's
'create_update' and 'object_list' modules, pushed inside a class.


Added: cs/portal/trunk/northridge/SeismoWebPortal/mezzanine.py
===================================================================
--- cs/portal/trunk/northridge/SeismoWebPortal/mezzanine.py	                        (rev 0)
+++ cs/portal/trunk/northridge/SeismoWebPortal/mezzanine.py	2008-05-02 03:14:00 UTC (rev 11897)
@@ -0,0 +1,338 @@
+
+
+from django.core.xheaders import populate_xheaders
+from django.template import loader, RequestContext
+from django import oldforms
+from django.db.models import FileField
+from django.contrib.auth.views import redirect_to_login
+from django.template import RequestContext
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
+from django.core.xheaders import populate_xheaders
+from django.core.paginator import ObjectPaginator, InvalidPage
+from django.utils.translation import gettext
+
+
+class Object(object):
+
+    @classmethod
+    def create(cls, request, model, template_name=None,
+               template_loader=loader, extra_context=None, post_save_redirect=None,
+               login_required=False, follow=None, context_processors=None):
+        """
+        Generic object-creation function.
+
+        Templates: ``<app_label>/<model_name>_form.html``
+        Context:
+            form
+                the form wrapper for the object
+        """
+        if extra_context is None: extra_context = {}
+        if login_required and not request.user.is_authenticated():
+            return redirect_to_login(request.path)
+
+        manipulator = model.AddManipulator(follow=follow)
+        if request.POST:
+            # If data was POSTed, we're trying to create a new object
+            new_data = request.POST.copy()
+
+            if model._meta.has_field_type(FileField):
+                new_data.update(request.FILES)
+
+            # Check for errors
+            errors = manipulator.get_validation_errors(new_data)
+            manipulator.do_html2python(new_data)
+
+            if not errors:
+                # No errors -- this means we can save the data!
+                new_object = manipulator.save(new_data)
+
+                if request.user.is_authenticated():
+                    request.user.message_set.create(message=gettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
+
+                # Redirect to the new object: first by trying post_save_redirect,
+                # then by obj.get_absolute_url; fail if neither works.
+                if post_save_redirect:
+                    return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
+                elif hasattr(new_object, 'get_absolute_url'):
+                    return HttpResponseRedirect(new_object.get_absolute_url())
+                else:
+                    raise ImproperlyConfigured("No URL to redirect to from generic create view.")
+        else:
+            # No POST, so we want a brand new form without any data or errors
+            errors = {}
+            new_data = manipulator.flatten_data()
+
+        # Create the FormWrapper, template, context, response
+        form = oldforms.FormWrapper(manipulator, new_data, errors)
+        if not template_name:
+            template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
+        t = template_loader.get_template(template_name)
+        c = RequestContext(request, {
+            'form': form,
+        }, context_processors)
+        for key, value in extra_context.items():
+            if callable(value):
+                c[key] = value()
+            else:
+                c[key] = value
+        return HttpResponse(t.render(c))
+
+    def update(self, request, model, object_id=None, slug=None,
+               slug_field=None, template_name=None, template_loader=loader,
+               extra_context=None, post_save_redirect=None,
+               login_required=False, follow=None, context_processors=None,
+               template_object_name='object'):
+        """
+        Generic object-update function.
+
+        Templates: ``<app_label>/<model_name>_form.html``
+        Context:
+            form
+                the form wrapper for the object
+            object
+                the original object being edited
+        """
+        if extra_context is None: extra_context = {}
+        if login_required and not request.user.is_authenticated():
+            return redirect_to_login(request.path)
+
+        # Look up the object to be edited
+        lookup_kwargs = {}
+        if object_id:
+            lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+        elif slug and slug_field:
+            lookup_kwargs['%s__exact' % slug_field] = slug
+        else:
+            raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
+        try:
+            object = model.objects.get(**lookup_kwargs)
+        except ObjectDoesNotExist:
+            raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
+
+        manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
+
+        if request.POST:
+            new_data = request.POST.copy()
+            if model._meta.has_field_type(FileField):
+                new_data.update(request.FILES)
+            errors = manipulator.get_validation_errors(new_data)
+            manipulator.do_html2python(new_data)
+            if not errors:
+                object = manipulator.save(new_data)
+
+                if request.user.is_authenticated():
+                    request.user.message_set.create(message=gettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
+
+                # Do a post-after-redirect so that reload works, etc.
+                if post_save_redirect:
+                    return HttpResponseRedirect(post_save_redirect % object.__dict__)
+                elif hasattr(object, 'get_absolute_url'):
+                    return HttpResponseRedirect(object.get_absolute_url())
+                else:
+                    raise ImproperlyConfigured("No URL to redirect to from generic create view.")
+        else:
+            errors = {}
+            # This makes sure the form acurate represents the fields of the place.
+            new_data = manipulator.flatten_data()
+
+        form = oldforms.FormWrapper(manipulator, new_data, errors)
+        if not template_name:
+            template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
+        t = template_loader.get_template(template_name)
+        c = RequestContext(request, {
+            'form': form,
+            template_object_name: object,
+        }, context_processors)
+        for key, value in extra_context.items():
+            if callable(value):
+                c[key] = value()
+            else:
+                c[key] = value
+        response = HttpResponse(t.render(c))
+        populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
+        return response
+
+    def delete(self, request, model, post_delete_redirect,
+               object_id=None, slug=None, slug_field=None, template_name=None,
+               template_loader=loader, extra_context=None,
+               login_required=False, context_processors=None, template_object_name='object'):
+        """
+        Generic object-delete function.
+
+        The given template will be used to confirm deletetion if this view is
+        fetched using GET; for safty, deletion will only be performed if this
+        view is POSTed.
+
+        Templates: ``<app_label>/<model_name>_confirm_delete.html``
+        Context:
+            object
+                the original object being deleted
+        """
+        if extra_context is None: extra_context = {}
+        if login_required and not request.user.is_authenticated():
+            return redirect_to_login(request.path)
+
+        # Look up the object to be edited
+        lookup_kwargs = {}
+        if object_id:
+            lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
+        elif slug and slug_field:
+            lookup_kwargs['%s__exact' % slug_field] = slug
+        else:
+            raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field")
+        try:
+            object = model._default_manager.get(**lookup_kwargs)
+        except ObjectDoesNotExist:
+            raise Http404, "No %s found for %s" % (model._meta.app_label, lookup_kwargs)
+
+        if request.method == 'POST':
+            object.delete()
+            if request.user.is_authenticated():
+                request.user.message_set.create(message=gettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
+            return HttpResponseRedirect(post_delete_redirect)
+        else:
+            if not template_name:
+                template_name = "%s/%s_confirm_delete.html" % (model._meta.app_label, model._meta.object_name.lower())
+            t = template_loader.get_template(template_name)
+            c = RequestContext(request, {
+                template_object_name: object,
+            }, context_processors)
+            for key, value in extra_context.items():
+                if callable(value):
+                    c[key] = value()
+                else:
+                    c[key] = value
+            response = HttpResponse(t.render(c))
+            populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
+            return response
+
+    @classmethod
+    def list(cls, 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):
+        """
+        Generic list of objects.
+
+        Templates: ``<app_label>/<model_name>_list.html``
+        Context:
+            object_list
+                list of objects
+            is_paginated
+                are the results paginated?
+            results_per_page
+                number of objects per page (if paginated)
+            has_next
+                is there a next page?
+            has_previous
+                is there a prev page?
+            page
+                the current page
+            next
+                the next page
+            previous
+                the previous page
+            pages
+                number of pages, total
+            hits
+                number of objects, total
+            last_on_page
+                the result number of the last of object in the
+                object_list (1-indexed)
+            first_on_page
+                the result number of the first object in the
+                object_list (1-indexed)
+        """
+        if extra_context is None: extra_context = {}
+        queryset = queryset._clone()
+        if paginate_by:
+            paginator = ObjectPaginator(queryset, paginate_by)
+            if not page:
+                page = request.GET.get('page', 1)
+            try:
+                page = int(page)
+                object_list = paginator.get_page(page - 1)
+            except (InvalidPage, ValueError):
+                if page == 1 and allow_empty:
+                    object_list = []
+                else:
+                    raise Http404
+            c = RequestContext(request, {
+                '%s_list' % template_object_name: object_list,
+                'is_paginated': paginator.pages > 1,
+                'results_per_page': paginate_by,
+                'has_next': paginator.has_next_page(page - 1),
+                'has_previous': paginator.has_previous_page(page - 1),
+                'page': page,
+                'next': page + 1,
+                'previous': page - 1,
+                'last_on_page': paginator.last_on_page(page - 1),
+                'first_on_page': paginator.first_on_page(page - 1),
+                'pages': paginator.pages,
+                'hits' : paginator.hits,
+            }, context_processors)
+        else:
+            c = RequestContext(request, {
+                '%s_list' % template_object_name: queryset,
+                'is_paginated': False
+            }, context_processors)
+            if not allow_empty and len(queryset) == 0:
+                raise Http404
+        for key, value in extra_context.items():
+            if callable(value):
+                c[key] = value()
+            else:
+                c[key] = value
+        if not template_name:
+            model = queryset.model
+            template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
+        t = template_loader.get_template(template_name)
+        return HttpResponse(t.render(c), mimetype=mimetype)
+
+    def detail(self, request, queryset, object_id=None, slug=None,
+               slug_field=None, template_name=None, template_name_field=None,
+               template_loader=loader, extra_context=None,
+               context_processors=None, template_object_name='object',
+               mimetype=None):
+        """
+        Generic detail of an object.
+
+        Templates: ``<app_label>/<model_name>_detail.html``
+        Context:
+            object
+                the object
+        """
+        if extra_context is None: extra_context = {}
+        model = queryset.model
+        if object_id:
+            queryset = queryset.filter(pk=object_id)
+        elif slug and slug_field:
+            queryset = queryset.filter(**{slug_field: slug})
+        else:
+            raise AttributeError, "Generic detail view must be called with either an object_id or a slug/slug_field."
+        try:
+            obj = queryset.get()
+        except ObjectDoesNotExist:
+            raise Http404, "No %s found matching the query" % (model._meta.verbose_name)
+        if not template_name:
+            template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
+        if template_name_field:
+            template_name_list = [getattr(obj, template_name_field), template_name]
+            t = template_loader.select_template(template_name_list)
+        else:
+            t = template_loader.get_template(template_name)
+        c = RequestContext(request, {
+            template_object_name: obj,
+        }, context_processors)
+        for key, value in extra_context.items():
+            if callable(value):
+                c[key] = value()
+            else:
+                c[key] = value
+        response = HttpResponse(t.render(c), mimetype=mimetype)
+        populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
+        return response
+
+
+# end of file

Modified: cs/portal/trunk/northridge/SeismoWebPortal/urls.py
===================================================================
--- cs/portal/trunk/northridge/SeismoWebPortal/urls.py	2008-05-02 02:33:07 UTC (rev 11896)
+++ cs/portal/trunk/northridge/SeismoWebPortal/urls.py	2008-05-02 03:14:00 UTC (rev 11897)
@@ -67,7 +67,7 @@
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 urlpatterns = patterns('',
-    (r'^$', 'SeismoWebPortal.views.home'),
+    (r'^$', 'SeismoWebPortal.views.root'),
 
     (r'^login/$', 'SeismoWebPortal.views.login_view'),
     (r'^logout/$', 'SeismoWebPortal.views.logout_view'),

Modified: cs/portal/trunk/northridge/SeismoWebPortal/views.py
===================================================================
--- cs/portal/trunk/northridge/SeismoWebPortal/views.py	2008-05-02 02:33:07 UTC (rev 11896)
+++ cs/portal/trunk/northridge/SeismoWebPortal/views.py	2008-05-02 03:14:00 UTC (rev 11897)
@@ -12,6 +12,7 @@
 
 import cmt
 import models
+import mezzanine
 
 import os, os.path
 from HTMLParser import HTMLParser
@@ -29,14 +30,67 @@
 login_required = user_passes_test(lambda u: not u.is_anonymous(), "/specfem3dglobe/login")
 
 
-def home(request):
+def root(request):
     if request.user.is_anonymous():
         return render_to_response('SeismoWebPortal/splash.html', {},
                                   RequestContext(request, {}))
-    return render_to_response('SeismoWebPortal/home.html',
-                              {},
-                              RequestContext(request, {}))
+    if not request.GET:
+        return render_to_response('SeismoWebPortal/home.html',
+                                  {},
+                                  RequestContext(request, {}))
 
+    try:
+        className = request.GET['class']
+    except KeyError:
+        raise Http404
+    
+    try:
+        Class = getattr(models, className)
+    except AttributeError:
+        raise Http404
+    
+    obj = mezzanine.Object()
+    objId = request.GET.get('object')
+    
+    if objId is None:
+        return obj.list(request, Class.objects.all())
+
+    try:
+        objId = int(objId)
+    except ValueError:
+        raise Http404
+
+    # NYI: mezzanine.Object instantiation belongs here.
+
+    action = request.GET.get('action')
+    if action is None:
+        return obj.detail(request, Class.objects.all(),
+                          object_id = objId)
+
+    if action == 'edit':
+        return obj.update(request, Class,
+                          object_id = objId)
+
+    if action == 'delete':
+        return obj.delete(request, Class, "/",
+                          object_id = objId)
+
+    # Brainstorm: fixed-target action buttons/menus
+    #     * create/new
+    #     * update/edit/(rename)
+    #     * delete
+    #     * upload
+    #     * search
+    #     * download {text/kml/pdf/ps}
+    #     * maps?
+    #   --- these depend upon implementation strategy ---
+    #     * save [live edit?]
+    #     * undo [trash?]
+    #     * help [probably css collapsable]
+
+    raise Http404
+
+
 def par_file(request, sim_id):
     from django.template import loader, Context
 



More information about the cig-commits mailing list