GrantU
GrantU

Reputation: 6555

Django Class Based View for both Create and Update

Say I want to create a Class Based View which both updates and creates an object. From a previous question I worked out I could do one of the following things:

1) Use 2 generic views CreateView and UpdateView which I think would mean having two URL's pointing to two different classes.

2) Use a class based view which inherits base View, which I think would mean having two URL's pointing to just 1 class (I created which inherits View).

I have two questions:

a) Which is better?

b) ccbv.co.uk shows a base View, but I don't see any get, post etc methods documented, is this correct?

Upvotes: 42

Views: 28782

Answers (9)

typonaut
typonaut

Reputation: 321

I think @paolo-melchiorre has the simplest solution, but it needs a bit of tweaking, since giving an invalid pk results in a new form (for an apparent ceate, which is probably not a great result from a UI/UX perspective). So, try this instead:

from django.views.generic import UpdateView

class CreateUpdateView(UpdateView):

    ...

    def get_object(self, queryset=None):
        if self.kwargs.get('pk'):
            '''
            it is a request for an object, which may or may not be valid
            '''
            try:
                return super().get_object(queryset)
            except AttributeError:
                # object not found
                raise Http404()
        else:
            '''
            there is no pk, so, the only other way to get here
            by the urls is as a "create", so return no object for
            form to render
            '''
            return None

Where the urls look like:

path('worker/create/', views.CreateUpdateView.as_view(), name='worker-create'),
path('worker/update/<pk>', views.CreateUpdateView.as_view(), name='worker-update'),

Upvotes: 0

Bindo
Bindo

Reputation: 11

In my case it was how to adapt the provided answers for UpdateView with Singleton. That's why I put hardcoded pk=1. With simplified class:

class MyClass(UpdateView):
    ...
    def get_object(self):
        obj, _ = MyClass.objects.update_or_create(pk=1)
        return obj

https://docs.djangoproject.com/en/4.2/ref/models/querysets/#update-or-create

Upvotes: 0

alex
alex

Reputation: 2541

In case you don't need to raise 404 and want all fields to be blank if the object doesn't exists, create object when first saving and updating when it exists you can use this.

views.py

from django.views.generic import UpdateView


class CreateUpdateView(UpdateView):
    model = MyModel
    form_class = MyModelForm

    def get_object(self, queryset=None):
        return self.model.objects.filter(...).first()

forms.py

class MyModelForm(forms.ModelForm):

    class Meta:
        model = MyModel
        fields = [...]

Upvotes: 3

vladtyum
vladtyum

Reputation: 71

Simplest and basically the best solution of all link

class WorkerUpdate(UpdateView):
    form_class = WorkerForm

    def get_object(self, queryset=None):

        # get the existing object or created a new one
        obj, created = Worker.objects.get_or_create(mac=self.kwargs['mac'])

        return obj

and that's it thanks @chriskief

Upvotes: 7

scubabuddha
scubabuddha

Reputation: 795

I ran into a situation where I wanted something like this. Here's what I came up with (do note that if you're trying to use it as an update view and it can't find the requested object, it'll behave as a create view rather than throwing a 404):

from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import ModelFormMixin, ProcessFormView

class CreateUpdateView(
    SingleObjectTemplateResponseMixin, ModelFormMixin, ProcessFormView
):

    def get_object(self, queryset=None):
        try:
            return super(CreateUpdateView,self).get_object(queryset)
        except AttributeError:
            return None

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(CreateUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(CreateUpdateView, self).post(request, *args, **kwargs)

It turns out that UpdateView and CreateView inherit from exactly the same classes and mixins. The only difference is in the get/post methods. Here's how they're defined in the Django source (1.8.2):

class BaseCreateView(ModelFormMixin, ProcessFormView):
    """
    Base view for creating an new object instance.

    Using this base class requires subclassing to provide a response mixin.
    """
    def get(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).post(request, *args, **kwargs)


class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
    """
    View for creating a new object instance,
    with a response rendered by template.
    """
    template_name_suffix = '_form'


class BaseUpdateView(ModelFormMixin, ProcessFormView):
    """
    Base view for updating an existing object.

    Using this base class requires subclassing to provide a response mixin.
    """
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(BaseUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(BaseUpdateView, self).post(request, *args, **kwargs)


class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):
    """
    View for updating an object,
    with a response rendered by template.
    """
    template_name_suffix = '_form'

As you can see, the CreateView get and post methods set self.object = None while the UpdateView sets it to self.get_object(). All I've done is combine those two in my CreateUpdateView.get_object method which attempts to call the parent class' get_object and returns None rather than raising an exception if there is no object.

To serve a 404 page when used as an update view, you could probably override as_view and pass it an update_only boolean argument. If update_only is True and the view can't find the object, then raise the 404.

Upvotes: 60

Paolo Melchiorre
Paolo Melchiorre

Reputation: 6122

Like suggested by @scubabuddha I ran into a similar situation and I used his answer modified as @mario-orlandi suggested in his comment:

from django.views.generic import UpdateView


class CreateUpdateView(UpdateView):

    def get_object(self, queryset=None):
        try:
            return super().get_object(queryset)
        except AttributeError:
            return None

I used this solution with Django 1.11 but I think it can work in Django 2.0.

Update

I confirm that this solution works with Django 2.0/2.1/2.2

Upvotes: 23

Risadinha
Risadinha

Reputation: 16661

To share code between your UpdateView and CreateView, instead of creating a combined class, you can use a common superclass as mixin. That way, it might be easier to separate the different concerns. And - you can re-use a lot of existing Django code.

class BookFormView(PJAXContextMixin):
    template_name = 'library/book_form.html'
    form_class = BookForm

    def form_valid(self, form):
        form.instance.owner = self.request.user
        return super().form_valid(form)

    class Meta:
        abstract = True


class BookCreateView(BookFormView, CreateView):
    pass


class FormatUpdateView(BookFormView, UpdateView):
    queryset = Book.objects

Upvotes: 0

iMitwe
iMitwe

Reputation: 1257

You can also use Django Smartmin which is inspired from CBV of Django. Here is an example from the documentation : https://smartmin.readthedocs.org/en/latest/quickstart.html

Upvotes: 0

Berislav Lopac
Berislav Lopac

Reputation: 17273

Why do you need to handle both create and update by a single View? It's much simpler to have two separate views, each inheriting from its respective generic view class. They can share the same form and template if you wish, and they're most likely served from different URLs, so I don't see what would you get by making it into a single view.

So: use two views, one inheriting from CreateView and the other from UpdateView. These handle pretty much everything you might need, while the second approach would require you to reinvent the wheel yourself. If cases when you have some common "housekeeping" code that is used both when creating or updating objects, the option of using a mixin, or you can perhaps create your own view that covers both use cases, inheriting from both CreateView and UpdateView.

Upvotes: 2

Related Questions