5

This is my updateview which is called by a URL with slug field, e.g.

/mymodel/update/<model_name_slug>/

MyModel have two fields, name and slug. The slug field <model_name_slug> is automatically generated from MyModel.name. If the user update it, a new slug field will be automatically generated. Then, I want to redirect to this new URL with the newly generated slug field.

The slug field auto-generation is working. It is implemented in the MyModelEditForm. However, the code below won't work. The reason is:

1) User typed this URL to update the existing model

/mymodel/update/example-model

2) User changes the Name attribute to "example model changed". Then slug field will be generated as "example-model-changed" in MyModel.

3) But the URL is not redirected to the "/mymodel/update/example-model-changed", as get_object() will return None. get() will not be able to compare the newly generated URL "example-model-changed" with original "example-model"

What am I missing in below code? I tried to access the newly update object using self.object, but have following error:

 MyModelUpdateView object has no attribute 'object'

Here is the code snippet:

class MyModelUpdateView(LoginRequiredMixin, UpdateView):
    model = MyModel
    form_class = MyModelUpdateForm
    template_name = 'mymodel/update_mymodel.html'

    def get_success_url(self):
        view_name = 'update_mymodel'
        return reverse_lazy(view_name, kwargs={'model_name_slug': self.kwargs.get('model_name_slug','')})

    def get_form_kwargs(self):
        '''
        This injects form with keyword arguments.
        '''
        kwargs = super(MyModelUpdateView, self).get_form_kwargs()
        #Update the kwargs with the user_id
        kwargs['user'] = self.request.user
        return kwargs

    def get(self, request, *args, **kwargs):
        # self.object = self.get_object()
        if self.request.path != self.object.get_absolute_url():
            return HttpResponseRedirect(self.object.get_absolute_url())
        else:
            return super(MyModelUpdateView, self).get(request,*args, **kwargs)
            # context = self.get_context_data(object=self.object)
            # return self.render_to_response(context)

    def get_object(self, queryset=None):
        instance = MyModel.objects.get(slug=self.kwargs.get('model_name_slug',''))
        return instance

Even if the get_success_url() is correctly updated, the get_object() seems to have two conflicting requirements: 1) Be able to fetch the correct object using existing "example-model" slug field; 2) Be able to fetch the updated object using newly generated "example-model-changed" before the existing URL is not redirected.

thinkdeep
  • 945
  • 1
  • 14
  • 32
  • As an aside, I think that if you set `slug_url_kwarg = 'model_name_slug'` on your view, you won't have to override the `get_object` method. – Alasdair Oct 14 '15 at 09:35
  • Do you mean the slug_url_kwarg will automatically be the newly generated slug field as shown in the get_success_url? – thinkdeep Oct 14 '15 at 14:18
  • No, this is separate from getting the slug in the `get_success_url` - for that, see my answer below. You've overridden `get_object` but you don't have to, just set `slug_url_kwarg = 'model_name_slug'` as a class attribute. – Alasdair Oct 14 '15 at 14:21
  • Thanks. I will give it a shot and let you know. BTW, I have figured out another way to do this and posted it below – thinkdeep Oct 14 '15 at 14:42

3 Answers3

6

You don't have to use self.get_object in the get_success_url method. The form_valid() sets self.object when saving the form, so you can use self.object.slug to get the new url.

def get_success_url(self):
    view_name = 'update_mymodel'
    # No need for reverse_lazy here, because it's called inside the method
    return reverse(view_name, kwargs={'model_name_slug': self.object.slug})
Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • This works. The one thing to notice here is that the order is --> form_valid() --> inside that there is save() --> After the save there is HttpResponseRedirect(self.get_success_url()), where you may use the updated slug (from self.object.slug). – Niko Föhr May 21 '17 at 13:10
0

Haven't used class based view much, but..

def get_success_url(self):
        view_name = 'update_mymodel'
        return reverse_lazy(view_name, kwargs={'model_name_slug': self.kwargs.get('model_name_slug','')})

If get_success_url is supposed to return the url that you want to redirect user to, shouldn't you use the updated slug not the old one?

self.get_object().model_name_slug

I guess this is correctly done atm.

1) Be able to fetch the correct object using existing "example-model" slug field;

You want to get the updated url based on updated model for redirect. Where do you create the updated url? get_success_url?. Then I don't see why reverse_lazy(view_name, kwargs={'model_name_slug': self.get_object().model_name_slug}) doesn't work?

2) Be able to fetch the updated object using newly generated "example-model-changed" before the existing URL is not redirected.

eugene
  • 39,839
  • 68
  • 255
  • 489
0

Thank Alasdair and eugene for the quick help.

Meanwhile, I have figured out another way to do this! That is, use two arguments in the URL, like and .

Look up in the stackoverflow URL. Have you noticed something similar? Yes, that is it. 33115530 is the pk, I guess. The rest is slug field automatically generated!

Then, in get_object, the newly updated object will be fetched through the , and get_success_url can use the new slug field updated in the object.

thinkdeep
  • 945
  • 1
  • 14
  • 32