app/django/views/generic/create_update.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     1 from django.forms.models import ModelFormMetaclass, ModelForm
       
     2 from django.template import RequestContext, loader
       
     3 from django.http import Http404, HttpResponse, HttpResponseRedirect
     1 from django.core.xheaders import populate_xheaders
     4 from django.core.xheaders import populate_xheaders
     2 from django.template import loader
       
     3 from django import oldforms
       
     4 from django.db.models import FileField
       
     5 from django.contrib.auth.views import redirect_to_login
       
     6 from django.template import RequestContext
       
     7 from django.http import Http404, HttpResponse, HttpResponseRedirect
       
     8 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
     5 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
     9 from django.utils.translation import ugettext
     6 from django.utils.translation import ugettext
    10 
     7 from django.contrib.auth.views import redirect_to_login
    11 def create_object(request, model, template_name=None,
     8 from django.views.generic import GenericViewError
       
     9 
       
    10 
       
    11 def apply_extra_context(extra_context, context):
       
    12     """
       
    13     Adds items from extra_context dict to context.  If a value in extra_context
       
    14     is callable, then it is called and the result is added to context.
       
    15     """
       
    16     for key, value in extra_context.iteritems():
       
    17         if callable(value):
       
    18             context[key] = value()
       
    19         else:
       
    20             context[key] = value
       
    21 
       
    22 def get_model_and_form_class(model, form_class):
       
    23     """
       
    24     Returns a model and form class based on the model and form_class
       
    25     parameters that were passed to the generic view.
       
    26 
       
    27     If ``form_class`` is given then its associated model will be returned along
       
    28     with ``form_class`` itself.  Otherwise, if ``model`` is given, ``model``
       
    29     itself will be returned along with a ``ModelForm`` class created from
       
    30     ``model``.
       
    31     """
       
    32     if form_class:
       
    33         return form_class._meta.model, form_class
       
    34     if model:
       
    35         # The inner Meta class fails if model = model is used for some reason.
       
    36         tmp_model = model
       
    37         # TODO: we should be able to construct a ModelForm without creating
       
    38         # and passing in a temporary inner class.
       
    39         class Meta:
       
    40             model = tmp_model
       
    41         class_name = model.__name__ + 'Form'
       
    42         form_class = ModelFormMetaclass(class_name, (ModelForm,), {'Meta': Meta})
       
    43         return model, form_class
       
    44     raise GenericViewError("Generic view must be called with either a model or"
       
    45                            " form_class argument.")
       
    46 
       
    47 def redirect(post_save_redirect, obj):
       
    48     """
       
    49     Returns a HttpResponseRedirect to ``post_save_redirect``.
       
    50 
       
    51     ``post_save_redirect`` should be a string, and can contain named string-
       
    52     substitution place holders of ``obj`` field names.
       
    53 
       
    54     If ``post_save_redirect`` is None, then redirect to ``obj``'s URL returned
       
    55     by ``get_absolute_url()``.  If ``obj`` has no ``get_absolute_url`` method,
       
    56     then raise ImproperlyConfigured.
       
    57 
       
    58     This function is meant to handle the post_save_redirect parameter to the
       
    59     ``create_object`` and ``update_object`` views.
       
    60     """
       
    61     if post_save_redirect:
       
    62         return HttpResponseRedirect(post_save_redirect % obj.__dict__)
       
    63     elif hasattr(obj, 'get_absolute_url'):
       
    64         return HttpResponseRedirect(obj.get_absolute_url())
       
    65     else:
       
    66         raise ImproperlyConfigured(
       
    67             "No URL to redirect to.  Either pass a post_save_redirect"
       
    68             " parameter to the generic view or define a get_absolute_url"
       
    69             " method on the Model.")
       
    70 
       
    71 def lookup_object(model, object_id, slug, slug_field):
       
    72     """
       
    73     Return the ``model`` object with the passed ``object_id``.  If
       
    74     ``object_id`` is None, then return the the object whose ``slug_field``
       
    75     equals the passed ``slug``.  If ``slug`` and ``slug_field`` are not passed,
       
    76     then raise Http404 exception.
       
    77     """
       
    78     lookup_kwargs = {}
       
    79     if object_id:
       
    80         lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
       
    81     elif slug and slug_field:
       
    82         lookup_kwargs['%s__exact' % slug_field] = slug
       
    83     else:
       
    84         raise GenericViewError(
       
    85             "Generic view must be called with either an object_id or a"
       
    86             " slug/slug_field.")
       
    87     try:
       
    88         return model.objects.get(**lookup_kwargs)
       
    89     except ObjectDoesNotExist:
       
    90         raise Http404("No %s found for %s"
       
    91                       % (model._meta.verbose_name, lookup_kwargs))
       
    92 
       
    93 def create_object(request, model=None, template_name=None,
    12         template_loader=loader, extra_context=None, post_save_redirect=None,
    94         template_loader=loader, extra_context=None, post_save_redirect=None,
    13         login_required=False, follow=None, context_processors=None):
    95         login_required=False, context_processors=None, form_class=None):
    14     """
    96     """
    15     Generic object-creation function.
    97     Generic object-creation function.
    16 
    98 
    17     Templates: ``<app_label>/<model_name>_form.html``
    99     Templates: ``<app_label>/<model_name>_form.html``
    18     Context:
   100     Context:
    19         form
   101         form
    20             the form wrapper for the object
   102             the form for the object
    21     """
   103     """
    22     if extra_context is None: extra_context = {}
   104     if extra_context is None: extra_context = {}
    23     if login_required and not request.user.is_authenticated():
   105     if login_required and not request.user.is_authenticated():
    24         return redirect_to_login(request.path)
   106         return redirect_to_login(request.path)
    25 
   107 
    26     manipulator = model.AddManipulator(follow=follow)
   108     model, form_class = get_model_and_form_class(model, form_class)
    27     if request.POST:
   109     if request.method == 'POST':
    28         # If data was POSTed, we're trying to create a new object
   110         form = form_class(request.POST, request.FILES)
    29         new_data = request.POST.copy()
   111         if form.is_valid():
    30 
   112             new_object = form.save()
    31         if model._meta.has_field_type(FileField):
       
    32             new_data.update(request.FILES)
       
    33 
       
    34         # Check for errors
       
    35         errors = manipulator.get_validation_errors(new_data)
       
    36         manipulator.do_html2python(new_data)
       
    37 
       
    38         if not errors:
       
    39             # No errors -- this means we can save the data!
       
    40             new_object = manipulator.save(new_data)
       
    41 
       
    42             if request.user.is_authenticated():
   113             if request.user.is_authenticated():
    43                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
   114                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
    44 
   115             return redirect(post_save_redirect, new_object)
    45             # Redirect to the new object: first by trying post_save_redirect,
   116     else:
    46             # then by obj.get_absolute_url; fail if neither works.
   117         form = form_class()
    47             if post_save_redirect:
   118 
    48                 return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
   119     # Create the template, context, response
    49             elif hasattr(new_object, 'get_absolute_url'):
       
    50                 return HttpResponseRedirect(new_object.get_absolute_url())
       
    51             else:
       
    52                 raise ImproperlyConfigured("No URL to redirect to from generic create view.")
       
    53     else:
       
    54         # No POST, so we want a brand new form without any data or errors
       
    55         errors = {}
       
    56         new_data = manipulator.flatten_data()
       
    57 
       
    58     # Create the FormWrapper, template, context, response
       
    59     form = oldforms.FormWrapper(manipulator, new_data, errors)
       
    60     if not template_name:
   120     if not template_name:
    61         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
   121         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
    62     t = template_loader.get_template(template_name)
   122     t = template_loader.get_template(template_name)
    63     c = RequestContext(request, {
   123     c = RequestContext(request, {
    64         'form': form,
   124         'form': form,
    65     }, context_processors)
   125     }, context_processors)
    66     for key, value in extra_context.items():
   126     apply_extra_context(extra_context, c)
    67         if callable(value):
       
    68             c[key] = value()
       
    69         else:
       
    70             c[key] = value
       
    71     return HttpResponse(t.render(c))
   127     return HttpResponse(t.render(c))
    72 
   128 
    73 def update_object(request, model, object_id=None, slug=None,
   129 def update_object(request, model=None, object_id=None, slug=None,
    74         slug_field='slug', template_name=None, template_loader=loader,
   130         slug_field='slug', template_name=None, template_loader=loader,
    75         extra_context=None, post_save_redirect=None,
   131         extra_context=None, post_save_redirect=None, login_required=False,
    76         login_required=False, follow=None, context_processors=None,
   132         context_processors=None, template_object_name='object',
    77         template_object_name='object'):
   133         form_class=None):
    78     """
   134     """
    79     Generic object-update function.
   135     Generic object-update function.
    80 
   136 
    81     Templates: ``<app_label>/<model_name>_form.html``
   137     Templates: ``<app_label>/<model_name>_form.html``
    82     Context:
   138     Context:
    83         form
   139         form
    84             the form wrapper for the object
   140             the form for the object
    85         object
   141         object
    86             the original object being edited
   142             the original object being edited
    87     """
   143     """
    88     if extra_context is None: extra_context = {}
   144     if extra_context is None: extra_context = {}
    89     if login_required and not request.user.is_authenticated():
   145     if login_required and not request.user.is_authenticated():
    90         return redirect_to_login(request.path)
   146         return redirect_to_login(request.path)
    91 
   147 
    92     # Look up the object to be edited
   148     model, form_class = get_model_and_form_class(model, form_class)
    93     lookup_kwargs = {}
   149     obj = lookup_object(model, object_id, slug, slug_field)
    94     if object_id:
   150 
    95         lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
   151     if request.method == 'POST':
    96     elif slug and slug_field:
   152         form = form_class(request.POST, request.FILES, instance=obj)
    97         lookup_kwargs['%s__exact' % slug_field] = slug
   153         if form.is_valid():
    98     else:
   154             obj = form.save()
    99         raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
       
   100     try:
       
   101         object = model.objects.get(**lookup_kwargs)
       
   102     except ObjectDoesNotExist:
       
   103         raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
       
   104 
       
   105     manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
       
   106 
       
   107     if request.POST:
       
   108         new_data = request.POST.copy()
       
   109         if model._meta.has_field_type(FileField):
       
   110             new_data.update(request.FILES)
       
   111         errors = manipulator.get_validation_errors(new_data)
       
   112         manipulator.do_html2python(new_data)
       
   113         if not errors:
       
   114             object = manipulator.save(new_data)
       
   115 
       
   116             if request.user.is_authenticated():
   155             if request.user.is_authenticated():
   117                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
   156                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
   118 
   157             return redirect(post_save_redirect, obj)
   119             # Do a post-after-redirect so that reload works, etc.
   158     else:
   120             if post_save_redirect:
   159         form = form_class(instance=obj)
   121                 return HttpResponseRedirect(post_save_redirect % object.__dict__)
   160 
   122             elif hasattr(object, 'get_absolute_url'):
       
   123                 return HttpResponseRedirect(object.get_absolute_url())
       
   124             else:
       
   125                 raise ImproperlyConfigured("No URL to redirect to from generic create view.")
       
   126     else:
       
   127         errors = {}
       
   128         # This makes sure the form acurate represents the fields of the place.
       
   129         new_data = manipulator.flatten_data()
       
   130 
       
   131     form = oldforms.FormWrapper(manipulator, new_data, errors)
       
   132     if not template_name:
   161     if not template_name:
   133         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
   162         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
   134     t = template_loader.get_template(template_name)
   163     t = template_loader.get_template(template_name)
   135     c = RequestContext(request, {
   164     c = RequestContext(request, {
   136         'form': form,
   165         'form': form,
   137         template_object_name: object,
   166         template_object_name: obj,
   138     }, context_processors)
   167     }, context_processors)
   139     for key, value in extra_context.items():
   168     apply_extra_context(extra_context, c)
   140         if callable(value):
       
   141             c[key] = value()
       
   142         else:
       
   143             c[key] = value
       
   144     response = HttpResponse(t.render(c))
   169     response = HttpResponse(t.render(c))
   145     populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
   170     populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
   146     return response
   171     return response
   147 
   172 
   148 def delete_object(request, model, post_delete_redirect,
   173 def delete_object(request, model, post_delete_redirect, object_id=None,
   149         object_id=None, slug=None, slug_field='slug', template_name=None,
   174         slug=None, slug_field='slug', template_name=None,
   150         template_loader=loader, extra_context=None,
   175         template_loader=loader, extra_context=None, login_required=False,
   151         login_required=False, context_processors=None, template_object_name='object'):
   176         context_processors=None, template_object_name='object'):
   152     """
   177     """
   153     Generic object-delete function.
   178     Generic object-delete function.
   154 
   179 
   155     The given template will be used to confirm deletetion if this view is
   180     The given template will be used to confirm deletetion if this view is
   156     fetched using GET; for safty, deletion will only be performed if this
   181     fetched using GET; for safty, deletion will only be performed if this
   163     """
   188     """
   164     if extra_context is None: extra_context = {}
   189     if extra_context is None: extra_context = {}
   165     if login_required and not request.user.is_authenticated():
   190     if login_required and not request.user.is_authenticated():
   166         return redirect_to_login(request.path)
   191         return redirect_to_login(request.path)
   167 
   192 
   168     # Look up the object to be edited
   193     obj = lookup_object(model, object_id, slug, slug_field)
   169     lookup_kwargs = {}
       
   170     if object_id:
       
   171         lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
       
   172     elif slug and slug_field:
       
   173         lookup_kwargs['%s__exact' % slug_field] = slug
       
   174     else:
       
   175         raise AttributeError("Generic delete view must be called with either an object_id or a slug/slug_field")
       
   176     try:
       
   177         object = model._default_manager.get(**lookup_kwargs)
       
   178     except ObjectDoesNotExist:
       
   179         raise Http404, "No %s found for %s" % (model._meta.app_label, lookup_kwargs)
       
   180 
   194 
   181     if request.method == 'POST':
   195     if request.method == 'POST':
   182         object.delete()
   196         obj.delete()
   183         if request.user.is_authenticated():
   197         if request.user.is_authenticated():
   184             request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
   198             request.user.message_set.create(message=ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name})
   185         return HttpResponseRedirect(post_delete_redirect)
   199         return HttpResponseRedirect(post_delete_redirect)
   186     else:
   200     else:
   187         if not template_name:
   201         if not template_name:
   188             template_name = "%s/%s_confirm_delete.html" % (model._meta.app_label, model._meta.object_name.lower())
   202             template_name = "%s/%s_confirm_delete.html" % (model._meta.app_label, model._meta.object_name.lower())
   189         t = template_loader.get_template(template_name)
   203         t = template_loader.get_template(template_name)
   190         c = RequestContext(request, {
   204         c = RequestContext(request, {
   191             template_object_name: object,
   205             template_object_name: obj,
   192         }, context_processors)
   206         }, context_processors)
   193         for key, value in extra_context.items():
   207         apply_extra_context(extra_context, c)
   194             if callable(value):
       
   195                 c[key] = value()
       
   196             else:
       
   197                 c[key] = value
       
   198         response = HttpResponse(t.render(c))
   208         response = HttpResponse(t.render(c))
   199         populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
   209         populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
   200         return response
   210         return response