42

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?

Paolo Melchiorre
  • 5,716
  • 1
  • 33
  • 52
GrantU
  • 6,325
  • 16
  • 59
  • 89

8 Answers8

60

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.

Paolo Melchiorre
  • 5,716
  • 1
  • 33
  • 52
scubabuddha
  • 795
  • 5
  • 9
  • It should. I'm trying to build a Wiki site, and that's exactly what I'm looking for. – art-solopov Feb 11 '16 at 21:35
  • 2
    Its even simpler than that; get_object will raise a AttributeError when neither pk or slug is provided but raise a DoesNotExist when and pk/slug was specified but could not be found. This means you don't need a update_only flag as your new get_object will only return None when no pk was specified, i.e. it will know when to Create and when to Update! :-) – Gert Steyn Jun 04 '16 at 13:37
  • 7
    Thank you ! Just wondering .. why not simply inherit from UpdateView and override get_object(), get() and post() as you did ? – Mario Orlandi Nov 01 '16 at 08:19
  • 1
    I took @MarioOrlandi's advice and just inherited from UpdateView. It is a good answer with lessons, though! – Bobort Feb 24 '17 at 21:27
  • This should be approved solution. Nice and Clean solution. I was trying to implement Detailview(Questions) with add update Formmixin (for answer to question). This really helped. – Lucky Apr 27 '20 at 09:21
21

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

Paolo Melchiorre
  • 5,716
  • 1
  • 33
  • 52
7

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

Risadinha
  • 16,058
  • 2
  • 88
  • 91
vladtyum
  • 71
  • 1
  • 1
  • 1
    I think this is perhaps the most elegant answer, but I'm wondering if there is an appropriate way to use `update_or_create` method instead? Doesn't seem right to use it within the `get_object` method, but...if it works? guess I'll just try it – ionalchemist May 13 '19 at 18:41
  • If you want to make it generic, you can set the user from the request as the object creator: obj, created = Setting.objects.get_or_create(user=self.request.user) – Dave Davis Jun 01 '21 at 22:00
  • there is a problem. get_or_create already creates a DB object (with an id). So if you use that in a CreateForm, and the user presses "Cancel", the bogus object already exists. – nerdoc Jan 26 '23 at 06:48
3

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 = [...]
alex
  • 2,381
  • 4
  • 23
  • 49
2

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.

Berislav Lopac
  • 16,656
  • 6
  • 71
  • 80
  • 3
    What if you don't know if the POST is going to result in a create or an update? How to route to the good view then (either the CreateView or UpdateView)? – lajarre Jan 07 '14 at 15:25
  • What do you mean you don't know? There is usually some information passed to the view -- e.g. the `id` -- if it's an update; if it's absent it's a new item. – Berislav Lopac Jan 07 '14 at 19:09
  • 6
    There is no absolute rule... You can also do some custom POST to an url with no `id` in it, and with no `id` in the POST data neither, and just expect that, based on the whole POST data, the server figures out by himself like an adult if it's a create or an update :) – lajarre Jan 07 '14 at 21:05
  • From the point #2 in your question above I have assumed that you can use a different URL for each situation; if so, you can simply work the ID in the URL. If you want to avoid that (say, for security reasons) and want to use a URL without an explicit id, taking its value from the POST values, you can override the `UpdateForm.get_object` method to extract id and query the model instance. – Berislav Lopac Jan 08 '14 at 01:15
  • 7
    I feel like the assumption "they're most likely served from different URLs" is completely wrong. A common URL design is that a `GET` on a URL for `objects` returns a list of those objects, and a `POST` to that same URL adds a new object of that type. – HorseloverFat May 12 '14 at 09:49
  • 2
    "It's much simpler to have two separate views" - I disagree. I have common model housekeeping code that needs to happen on both a create and update. I don't see why there shouldn't be a CreateOrUpdateView. The url may contain a slug rather than the id. – jozxyqk May 21 '15 at 06:23
  • @jozxyqk -- For common code like that there is 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`. – Berislav Lopac May 21 '15 at 07:40
  • Thanks. It sounds like both should be part of the answer. The mixin idea seems right for my problem, though I'm still fumbling around with how I'd design it. A one-view-for-both sounds exactly what the OP is after, though I would have thought inheriting from Create/UpdateView would introduce conflicts. – jozxyqk May 21 '15 at 08:40
  • Good point, I have updated the answer. Regarding conflicts, a good thing is that Python allows for multiple inheritance, as long as you understand which classes take precedence when overriding code. More details here: https://www.python.org/download/releases/2.3/mro/ – Berislav Lopac May 21 '15 at 17:20
  • 8
    Bummer that SO has so many answers like this one. Basically "nah you shouldn't try to do it that way, OP, because it's not what I usually do and I don't want to think about it your way." – foobarbecue Aug 19 '17 at 07:09
0

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

iMitwe
  • 1,218
  • 16
  • 35
0

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
Risadinha
  • 16,058
  • 2
  • 88
  • 91
0

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

Bindo
  • 1
  • 2