Dynamically altering a (ForeignKey) ModelChoiceField’s values

1 510 просмотров
Перейти к первому непрочитанному сообщению

Jim Illback

не прочитано,
25 апр. 2018 г., 13:42:3325.04.2018
– Django users

I wondered if anyone has had to alter the queryset behind a model form’s foreign key field which presents as a model choice field?

Briefly, I have a client attribute table with the foreign key to a chore/time table. For an add of the client attribute table, I want to limit entries to unassigned chore/time combinations only. This works perfectly during my CreateView. Here are some extracts to show specifics:

Models.py:

class ChoreTime(models.Model):

chore = models.ForeignKey(Chore, on_delete=models.CASCADE)

time = models.ForeignKey(TimePeriod, on_delete=models.CASCADE)

priority = models.BooleanField(default=False)

class Checkin(models.Model):

client = models.ForeignKey(Client, on_delete=models.CASCADE)

choretime = models.ForeignKey(ChoreTime, on_delete=models.CASCADE)

Forms.py:

class CheckinForm(forms.ModelForm):

assigned_qs = Checkin.objects.values(‘choretime').filter(choretime_id__isnull=False)

choretime = forms.ModelChoiceField(queryset=ChoreTime.objects.exclude(pk__in=assigned_qs))

However, I cannot get the any design to work on an UpdateView form. Using the same form as above, the current value does not even show up – it is blank – because, of course, that entry is already assigned so is excluded.

What I’d like is to have the above exclusions BUT be able to also add in the single entry assigned to the client being updated – so the entry will show that specific assignment (as well as limiting any possible change options to unassigned values - just like on the Create).

The problems with various approaches I’ve tried are:

1.     Anything done before the form is fully assembled won’t have the existing form’s Checkin ID value (which is part of the URL, just BTW). This is needed to look up and add the existing entry. So, having a database view won’t work without being able to communicate the existing person’s assignment ID to the view. Similarly, using an override queryset on the form, like done above for the Create, needs that ID, too.

2.     If I do the queries in the class's GET method routine as ORM objects, I must use UNION (a union of the exclusions as above plus the existing update client’s assignment). That union option keeps giving an error that one of the ORM querysets is not a queryset. However, they are both using “<class>.objects.filter…”. It seems like complex queries don’t work with the union command. If I use raw SQL, the query works but the assignment to the choretime field’s queryset fails.

Does anyone have experience with this sort of behavior and be willing to give guidance?

Jim Illback

не прочитано,
27 апр. 2018 г., 12:16:0727.04.2018
– Django users
To make this easier, here is just a simple the question.

Does Django give a method that can be overridden while processing a form which has access to the incoming URL parameters and precedes the display of the form’s fields, specifically being able to alter the fields’s query set before it is displayed?

Thanks for any help to answer this question.

Jim Illback

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/F0B95720-C669-4CE5-912E-5167E3B89DFE%40hotmail.com.
For more options, visit https://groups.google.com/d/optout.

Matthew Pava

не прочитано,
27 апр. 2018 г., 12:21:4127.04.2018
– django...@googlegroups.com

In the form’s __init__ method, you’ll have to modify the queryset of the field.

 

def __init__(self, *args, **kwargs):

                super().__init__(*args, **kwargs)

                self.fields[‘choretime’].queryset |= ChoiceTime.objects.filter(pk=self.instance.pk)

 

I hope that helps!

Jim Illback

не прочитано,
27 апр. 2018 г., 12:34:1027.04.2018
– Django users
I also need to have the specific ID specified in the URL, so that is the other part needed. I’ve successfully done your suggestion for CreateView. Now, I just can’t seem to get UpdateView to work properly.

Thanks much!
Jim

Matthew Pava

не прочитано,
27 апр. 2018 г., 14:03:0627.04.2018
– django...@googlegroups.com

That ID in the URL should be taken care of in the view, not the form.  You pass the object in as keyword instance to instantiate the form.  How are you defining your form in the view?

 

Typically, you would do something like this:

 

instance = CheckIn.objects.get(pk=self.kwargs.get(‘pk’))

form = CheckInForm(request.POST or None, instance=instance)

Jim Illback

не прочитано,
27 апр. 2018 г., 14:40:1227.04.2018
– Django users
Absolutely agree. The “instance” (your note) is for Checkin and it includes a ForeignKey field to ChoreTime, which is a selection (choice field). But, I want that choice field to be limited for the forms to be only what has been not assigned yet. That way the user on the page will know that any shown pick is available. Otherwise, there are over 100 choices and no guidance as to what is truly available or already filled.

I have to alter the query set for that field. On a CreateView, I have it working perfectly (it’s shown below). However, on an UpdateView, the person’s choice is one of the “unavailable”. So, I’m trying to add just that one choice back into the query set before showing it on the form. Therefore, I need both the URL (to get the update’s choice) and the pre-form display method override (to exclude assigned entries) where I can override the field’s query set.

Thanks for the inputs, Matthew. I hope I’ve made it clearer. If you think there’s a better overall approach, I’m all ears!
Jim

Matthew Pava

не прочитано,
27 апр. 2018 г., 14:52:1327.04.2018
– django...@googlegroups.com

Well, Jim, my first message should have addressed your issue with the UpdateView.

Your URL keyword argument should be pk by default.  If it’s not, you have to set pk_url_kwarg in UpdateView.

Example:  /checkin/update/(?<pk>)

 

https://ccbv.co.uk/projects/Django/2.0/django.views.generic.edit/UpdateView/

 

class CheckinUpdate(UpdateView):

      model = Checkin

      form_class = CheckinForm

 

 

class CheckinForm(forms.ModelForm):

assigned_qs = Checkin.objects.values(‘choretime').filter(choretime_id__isnull=False)

choretime = forms.ModelChoiceField(queryset=ChoreTime.objects.exclude(pk__in=assigned_qs))

 

      def __init__(self, *args, **kwargs):

            super().__init__(*args, **kwargs)

            # if an instance has been passed in, we need to include its value in the ChoreTime queryset

            if hasattr(self, “instance”) and self.instance:

                  self.fields[‘choretime’].queryset |= ChoreTime.objects.filter(pk=self.instance.choretime.pk)

Jim Illback

не прочитано,
30 апр. 2018 г., 13:10:0830.04.2018
– Django users
If anyone is interested in a solution I found, it is at www.webforefront.com/django/formprocessing.html. Great documentation there, and you can read why it works, too.

Here are some code snippets used to test my situation.

In forms.py and in a unique form for update (not create) - JimtestUpdateForm:
    def __init__(self, *args, **kwargs):
        super(JimtestUpdateForm, self).__init__(*args, **kwargs) # invoke the default first
        initial_arguments = kwargs.get('initial', None)
        if initial_arguments:
            ci_id = initial_arguments.get('ci_id', None)
            if ci_id:
                assign_qs = Checkin.objects.values('choretime').filter(choretime_id__isnull=False)
                unassign_qs = ChoreTime.objects.exclude(pk__in=assign_qs)
                ci_qs = Checkin.objects.get(id=ci_id)
                ct_qs = ChoreTime.objects.filter(id=ci_qs.choretime_id)
                new_qs = unassign_qs | ct_qs # union unassigned with the client’s single assignment
                self.fields['choretime'].queryset = new_qs # update the choices shown
                self.fields['choretime'].widget.choices = self.fields['choretime'].choices # update the widget (may not need this - just a hunch - not tested omitting it yet

In views.py under the CB dispatch method:
        if request.method == ‘GET’:    
form = JimtestUpdateForm(instance=checkin, initial={'ci_id': checkinid}) # pass in the value(s) needed in __init__()
...

That is pretty much it - simple if you have the proper documentation on how to initialize forms.

Jim

Jim
To make this easier, here is just a simple question. 
Ответить всем
Отправить сообщение автору
Переслать
0 новых сообщений