from django.template import RequestContext, loader from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.core.exceptions import ObjectDoesNotExist from django import forms class CreateObjectView(object): """ Generic view to create instances of a model. """ def __call__(self, request, model=None, form_class=None, template_name=None, extra_context=None, post_save_redirect=None): """ Create a new object using a ModelForm. Accepts arguments: ``request`` The HttpRequest object. ``model`` Model type to create (either this or form_class is required) ``form_class`` ModelForm subclass to use (either this or model is required) ``template_name`` name of template to use, or list of templates - defaults to /_form.html ``extra_context`` dictionary of items and/or callables to add to template context. ``post_save_redirect`` URL to redirect to after successful object save. If post_save_redirect is None or an empty string, default is to send to the instances get_absolute_url method or in its absence, the site root. """ self.request = request (self.model, self.form_class) = self.get_model_and_form_class( model, form_class) form = self.get_form(request, self.form_class) if request.method == 'POST' and form.is_valid(): return self.get_redirect(post_save_redirect, self.save_form(form)) c = self.apply_extra_context(extra_context, self.get_context(request, {'form': form})) t = self.get_template(self.model, template_name) return self.get_response(t, c) def get_model_and_form_class(self, _model, form_class): """ Return a model and form class based on model or form_class argument. """ if _model is None: try: _model = form_class._meta.model except AttributeError: raise ValueError("%s requires either model or form_class" % (self.__class__.__name__,)) if form_class is None: class Meta: model = _model class_name = _model.__name__ + 'Form' form_class = forms.models.ModelFormMetaclass( class_name, (forms.ModelForm,), {'Meta': Meta}) return (_model, form_class) def apply_extra_context(self, extra_context, context): """ Add items from extra_context dict to the given context, calling any callables in extra_context. Return the updated context. """ extra_context = extra_context or {} for key, value in extra_context.iteritems(): if callable(value): context[key] = value() else: context[key] = value return context def get_form_kwargs(self, request): """ Get dictionary of arguments to construct the appropriate ``form_class`` instance. """ if request.method == 'POST': return {'data': request.POST, 'files': request.FILES} return {} def get_form(self, request, form_class): """ Return the appropriate ``form_class`` instance based on the ``request``. """ return form_class(**self.get_form_kwargs(request)) def save_instance(self, obj): """ Save and return model instance. """ obj.save() return obj def save_form(self, form): """ Save form, returning saved object. """ return self.save_instance(form.save(commit=False)) def get_redirect(self, post_save_redirect, obj): """ Return a HttpResponseRedirect based on ``post_save_redirect`` argument and just-saved object ``obj``. """ if not post_save_redirect: if hasattr(obj, 'get_absolute_url'): post_save_redirect = obj.get_absolute_url() else: post_save_redirect = "/" return HttpResponseRedirect(post_save_redirect) def get_template(self, model, template_name): """ Return a template to use based on ``template_name`` and ``model``. """ template_name = template_name or "%s/%s_form.html" % ( model._meta.app_label, model._meta.object_name.lower()) if isinstance(template_name, (list, tuple)): return loader.select_template(template_name) else: return loader.get_template(template_name) def get_context(self, request, dictionary): """ Return a context instance with data in ``dictionary``. """ return RequestContext(request, dictionary) def get_response(self, template, context_instance): """ Return a HttpResponse object based on given request, template, and context. """ return HttpResponse(template.render(context_instance)) class UpdateObjectView(CreateObjectView): """ Generic view to update instances of a model. """ def __call__(self, request, object_id=None, slug=None, slug_field='slug', *args, **kwargs): """ Update an existing object using a ModelForm. Accepts same arguments as CreateObjectView, and also: ``object_id`` id of object to update (either this or slug+slug_field is required) ``slug`` slug of object to update (either this or object_id is required) ``slug_field`` field to look up slug in (defaults to ``slug``) """ self.object_id = object_id self.slug = slug self.slug_field = slug_field return super(UpdateObjectView, self).__call__(request, *args, **kwargs) def get_model_and_form_class(self, *args, **kwargs): """ Wrap parent ``get_model_and_form_class`` and save the model class so we can get to it in get_form_args. """ ret = super(UpdateObjectView, self).get_model_and_form_class(*args, **kwargs) self.model = ret[0] return ret def get_form_kwargs(self, request): instance = self.lookup_object(self.model, self.object_id, self.slug, self.slug_field) kwargs = super(UpdateObjectView, self).get_form_kwargs(request) kwargs['instance'] = instance return kwargs def lookup_object(self, model, object_id, slug, slug_field): """ Find and return an object of type ``model`` based on either the given ``object_id`` or ``slug`` and ``slug_field``. """ 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 ValueError("%s requires object_id or slug+slug_field" % (self.__class__.__name__,)) try: return model.objects.get(**lookup_kwargs) except ObjectDoesNotExist: raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)