How to skip the form display on CreateView

If you have an object, “Status”, and you want to have a button from a template create a new Status instance automatically, can you just use the CreateView, but “skip” the form part?

In my setup, Status is related to a Client, and also to a Note. So, a Client has multiple Notes, and the Notes have multiple Statuses.

When I click the link/button for “Approve”, I can confirm the new Status instance is created, but I am still displaying the “generic_add.html” form to the user, which I don’t want to do. Ideally, I just want to create the new Status, then return back to the client record, and show a “success message” of “Note approved”.

I’m guessing there’s a way to do this without using the CreateView setup, but I also like having the “permissions_required” easily applied.

Thank you for any guidance you can offer!

models.py:

class Status():
    client = models.ForeignKey(Client, on_delete=models.CASCADE)
    note = models.ForeignKey(Note, on_delete=models.CASCADE)
    approval_status = models.CharField(max_length=50, choices=ApprovalStatus.choices, default='')
    comments = models.TextField(blank=True, default='')

views.py:

class StatusApprove(UserAccessMixin, SuccessMessageMixin, generic.CreateView):
    permission_required = 'clients.add_status'

    model = Status
    form_class = StatusForm
    template_name = 'generic_add.html'
    success_message = model._meta.verbose_name + " added successfully."

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(StatusApprove, self).get_context_data(**kwargs)

        # Grab the Client and Note passed in from urls.py
        client_id = self.kwargs['client_id']
        note_id = self.kwargs['note_id']

        # Grab the Client and Note instance based on the ID passed in from urls.py
        client = Client.objects.get(id=client_id)
        note = Note.objects.get(id=note_id)

        # Create a new Status instance of Approved
        status = Status.objects.create(client=client, note=note, approval_status='Approved')

        return context

    def get_success_url(self):
        return reverse('clients:detail', args=[str(self.kwargs['client_id'])])

urls.py:

path('<int:client_id>/status/<int:note_id>/approve', views.StatusApprove.as_view(), name="status_approve"),

forms.py:

class StatusForm(ModelForm):
	class Meta:
		model = Status
		exclude = ['client', 'note']

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.helper = FormHelper()
		self.helper.layout = Layout(
			Fieldset('',
				'approval_status',
				'comments',
			),
			Hidden('hidden_client_id', '{{ c_id }}'),
			Hidden('hidden_note_id', '{{ note_id }}'),
			ButtonHolder(
				Submit('submit', 'Save'),
				HTML("<a href='{{ view.get_success_url }}' class='btn btn-light'>Cancel</a>")
			)
		)

template.html:

<a href="{% url 'clients:status_approve' client.pk note.pk %}">Approve</a>

Is there any data being submitted from this form?

If not, and you still want to keep with the generic views, you could:

  • Change your buttons to be links and not submit forms
  • Inherit from RedirectView
  • Override the get method to perform whatever validation you need to perform on the submission to ensure the right person is taking the action.
  • Make whatever database updates are desired
  • Set the success message
  • and allow the view to redirect you to “the client record”.

If there is a form submitting data along with the button press, then you would be overriding the post method and not the get.

1 Like

Thank you, Ken!

There is data being submitted via the form, but I didn’t want to display the form, just force the value of the data that is being submitted.
Here is what I ended up doing based on your recommendations.
This is working, but open to any other improvements if I did something wrong. Otherwise, I would consider this solved!

Also, is there anyway I can buy you a beer/coffee for your steady impressive work teaching all of us on this forum??

views.py:

class StatusApprove(UserAccessMixin, SuccessMessageMixin, RedirectView):
    permission_required = 'clients.add_status'
    success_message = "Note approved."

    # Attributes needed for RedirectView
    permanent = False
    query_string = False
    # pattern_name = "client-detail" <-- Don't need this, because we are using return reverse below

    def get_redirect_url(self, *args, **kwargs):
        # Grab the Client and Note passed in from urls.py
        client_id = self.kwargs['client_id']
        note_id = self.kwargs['note_id']

        # Grab the Client and Note instance based on the ID passed in from urls.py
        client = Client.objects.get(id=client_id)
        note = Note.objects.get(id=note_id)

        # Create a new Status instance of Approved
        status = Status.objects.create(client=client, note=note, approval_status='Approved')

        # Set a success message
        messages.success(self.request, self.success_message)

        # return super().get_redirect_url(*args, **kwargs) <-- You would use this if you wanted to use the pattern_name attribute above
        return reverse('clients:detail', args=[str(self.kwargs['client_id'])])

That’s very kind of you to say, but no. This is just my way of giving back to the community that has supported me so well for the past 10+ years.

1 Like