How to get parent ID for queryset filter in form load of CreateView

Hello,

I’m using Class Based Views and trying to get a parent ID in the initial load of the form of a CreateView. I’ve looked through the documentation suggested in other posts (https://ccbv.co.uk/) and (Django Class Based Views Diagrams), but am still unsure of how to accomplish this.

It doesn’t matter if I even need to just use part of the URL to get the parent ID I’m looking for, I’m looking for any way to make this work.

I have a parent object, Client, which has an ID in the url, when creating a child, Note, object. Like this: http://127.0.0.1:8000/clients/1/note/add
In that case, the “1” is the Client ID that I want to get when the “Add Note” form is loading.

Here is the urls.py part of this:
path('<int:client_id>/note/add', views.NoteAdd.as_view(), name="note_add"),

Here is part of the forms.py code that is creating the multi-select field for the Note object. Essentially, I would like to filter the query set for the ‘goals’ field to only include Objective records for the parent Client ID of this object.

forms.py

class NoteForm(ModelForm):
	class Meta:
		model = Note
		exclude = ['client']
		widgets = {
			'service_date': widgets.DateInput(attrs={'type': 'date'}),
			'start_time': widgets.TimeInput(attrs={'type': 'time'}),
			'end_time': widgets.TimeInput(attrs={'type': 'time'}),
		}

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.helper = FormHelper()

		# Create multi-select checkboxes that only have the values for this specific client
		self.fields['goals'] = ModelMultipleChoiceField(
				#queryset=Objective.objects.filter(client=kwargs.get('instance').client.id),
				queryset=Objective.objects.filter(client=self.kwargs['client_id']),
				#queryset=Objective.objects.filter(client=client_id),
				widget=CheckboxSelectMultiple
			)

You can see the multiple (commented out) ways I’ve tried to access that “client_id” on the NoteForm when it first loads. This obviously works fine when editing a previously saved NoteForm, but not on the initial creation of a new Note.

Here is the Note object in models.py

class Note(TheBaseClass):
    client = models.ForeignKey(Client, on_delete=models.CASCADE)
    service_date = models.DateField()
    start_time = models.TimeField(blank=True, null=True)
    end_time = models.TimeField(blank=True, null=True)
    goals = models.ManyToManyField("Objective", verbose_name="Goals addressed")

I’m not sure if this helps, but here is part of the views.py file as well.

class NoteAdd(UserAccessMixin, SuccessMessageMixin, generic.CreateView):
    permission_required = 'clients.add_note'

    model = Note
    form_class = NoteForm
    template_name = 'generic_add.html'
    success_message = model._meta.verbose_name + " added successfully."

The ultimate error message is:

# AttributeError at /clients/1/note/add
'NoteForm' object has no attribute 'kwargs'

This makes sense, because I suppose kwargs don’t exist yet at the time the form is being rendered, but I’m looking for a way to get that “client_id” from the URL and use that in the queryset filter.

Knowing all of this, how do I get the “client_id” (present in the url) to be used on NoteAdd load?

All named url parameters are made available in a CBV through the self.kwargs object. See the docs and examples at Built-in class-based generic views | Django documentation | Django

This would be accessed in one of the CBV functions being called - it cannot be used at the module level.
So you might take advantage of this in one of get_queryset, get_object, get_context_data, etc - wherever you need to reference it in the process of constructing your response.

This is where using tools like the CCBV site can be really helpful for understanding the flow of a CBV and learning where you might want to hook in some custom functionality.

Thank you for the response.
After reviewing the documentation, I still don’t see something that covers the specifics of what I’m looking for.

As I understand it, the get_queryset function is being used to filter the current object. In the example I gave, that would be the Note object.

But on that Note object’s form in the “goals” field, I want to filter the queryset for the parent object.

As in, the Note object’s “goals” field, needs to be filtered to only include the parent “Client” object’s (in this case ID: 1) goals.

From my understanding you want to access the view’s kwargs from within the form. While you can’t do that out of the box, you could pass the value specifically to the form. This can be done by adding it to the return value of get_form_kwargs in your view. Like Ken mentioned, it’s easier to reason about the view’s functions using the Classy CBV site.

@CodenameTim Thank you so much for pointing me in the right direction of a function I could try!
This is working!

updated views.py class NoteAdd

def get_form_kwargs(self):
	"""Return the keyword arguments for instantiating the form."""
	kwargs = super().get_form_kwargs()
	# Adding in client_id to the kwargs on load, so the forms.py NoteForm can access it.
	client_id_from_url = self.kwargs['client_id']

	if hasattr(self, "object"):
		# Normal syntax
		#kwargs.update({"instance": self.object})

		# Adding in client_id_from_url as a new parameter for the __init__ in forms.py NoteForm
		kwargs.update({"instance": self.object,
					   "client_id_from_url": client_id_from_url})
	return kwargs

Then, over in forms.py class NoteForm
def __init__(self, client_id_from_url, *args, **kwargs):

allowed me to use the new client_id_from_url variable:

# Create multi-select checkboxes that only have the values for this specific client
self.fields['goals'] = ModelMultipleChoiceField(
		queryset=Objective.objects.filter(client=client_id_from_url),
		widget=CheckboxSelectMultiple
)

Now, simply out of wanting to understand better, why am I able to access self.kwargs['client_id'] inside of the views.py NoteAdd class, but not in the forms.py NoteForm __init__ function?

I realize “self” is different in both cases, but it would seem if the “client_id” kwarg is available to the NoteAdd’s get_form_kwargs function, and the NoteForm’s __init__ function is using the get_form_kwargs function, then shouldn’t the client_id kwarg already exist?

Or is it that the NoteForm’s __init__ function is the first thing being called, which then fires the get_form_kwargs function?

self.kwargs for the view is set from the View.setup. It’s specific to Views. Forms don’t have that same setup. Forms are somewhat removed from the concept of a request, response and view. Does that help clear up the discrepancy? If not, please let me know where I should explain more.

I think this helps explain it some. And the rest will come from time/experience, probably. :slight_smile:

Thank you, again, for your help!

You can use the python debugger or your IDE’s debugger to step through a view and form’s process. That can help you understand how it all works together.

Most of my knowledge came from diving into the code and/or repeatedly reading the docs trying to solve my problems and others’.

1 Like