AndrewIngram / django-extra-views

Django's class-based generic views are awesome, let's have more of them.
MIT License
1.38k stars 172 forks source link

How to pass URL Vars to from InlineFormSetFactory (views.py) to ModelForms (forms.py) #252

Closed Maximost closed 1 year ago

Maximost commented 1 year ago

This is probably an easy question but I am wondering how to pass URL variables from InlineFormSetFactory classes in views.py to the respective ModelForm in forms.py. The specific scenario I'm having trouble with is in relation to UpdateWithInlinesView. I have tried the various kwarg.get/kwarg.pop approaches but these lead to key errors. Is it not possible to propagate url vars to the inline formsets in this way?

Thank you for any insight you might have.

sdolemelipone commented 1 year ago

Hi! self.kwargs should work just fine. In fact ModelFormWithInlinesMixin.construct_inlines() already passes them directly into the constructor of each InlineFormSetFactory subclass defined in the inlines attribute of the view. Can you post the code which isn't working?

sdolemelipone commented 1 year ago

E.g.



class MyFactory(InlineFormSetFactory):
    def some_method(self):
        foo = self.kwargs.get("my_url_variable")
        ...

class MyView(UpdateWithInlinesView):
    inlines = [MyFactory]
    ...
Maximost commented 1 year ago

Thank you for the quick response. Basically, I am trying to filter the option select user list based on a combined list of user ids. I have this working fine in all my add and edit views. It is the UpdateWithInlinesView that does not seem to be working. I suspect there is something simple that I'm missing.

The code I'm wrestling with is below:

# VIEWS.PY
# Inline Form Constructor
class task_edit_inline(InlineFormSetFactory):
  form_class = taskEditForm
  model = Tasks
  factory_kwargs = {'extra': 0, 'max_num': None, 'can_order': False, 'can_delete': True}

  def get_formset_kwargs(self, **kwargs):
    kwargs = super(task_edit_inline, self).get_formset_kwargs(**kwargs)

    # Create user list for option select filtering in forms.py
    project_id = self.object.id
    pp = Project.objects.filter(id = project_id).values_list('parent_project_id', flat=True)[0]
    users_1 = ParentTeam.objects.filter(parent = pp).values_list('user', flat=True)
    users_2 = ProjectTeam.objects.filter(project = project_id).values_list('user', flat=True)
    kwargs["my_users"] = list(chain(users_1, users_2))

    # Order tasks
    kwargs["queryset"] = self.object.tasks_set.order_by("task_order", "task_due")
    return kwargs`

# Grid editing view
class task_grid_edit_if_view(SuccessMessageMixin, UpdateWithInlinesView):
  model = Project
  template_name = 'otis/edit_task_grid-if.html'
  form_class = TaskGridForm
  inlines = [task_edit_inline]
  success_message = "Tasks updated"

  def get_context_data(self, **kwargs):
    context = super(task_grid_edit_if_view, self).get_context_data(**kwargs)
    return context

# FORMS.PY

# Edit Form
class taskEditForm(ModelForm):
  def __init__(self, *args, **kwargs):

    my_users = kwargs.pop('my_users')
    self.base_fields['user'].queryset = User.objects.filter(id__in=my_users).order_by('first_name')

    super(taskEditForm, self).__init__(*args, **kwargs)
    self.label_suffix = ""

  class Meta:
    model = Tasks
    fields = ('task_order','user','task_type','task_note','task_due','task_effort','is_complete')
    widgets = {
      'task_order': NumberInput(attrs={'size': '2'}),
      'task_due': NumberInput(attrs={'type': 'date'}),
      'task_note': Textarea(attrs={'cols': 20, 'rows': 5, 'placeholder': 'Task details...'}),
      }
sdolemelipone commented 1 year ago

Looks like you're passing my_users to the formset constructor but you want to process it in the form constructor. I think you're looking for the below? https://django-extra-views.readthedocs.io/en/latest/pages/formset-customization.html?highlight=form_kwargs#passing-arguments-to-the-form-constructor

Maximost commented 1 year ago

Thank you so much for your help! I clearly did not understand how to pass vars to the form constructor. So just to clarify for my own sanity (and maybe others who stumble on this), I made the following modifications to the get_formset_kwargs function in the inlineformset constructor:

  def get_formset_kwargs(self, **kwargs):
    kwargs = super(task_edit_inline, self).get_formset_kwargs(**kwargs)

    # Create user list for option select filtering in forms.py
    project_id = self.object.id
    pp = Project.objects.filter(id = project_id).values_list('parent_project_id', flat=True)[0]
    users_1 = ParentTeam.objects.filter(parent = pp).values_list('user', flat=True)
    users_2 = ProjectTeam.objects.filter(project = project_id).values_list('user', flat=True)
    user_list = list(chain(users_1, users_2))
    # change here --- \/
    kwargs['form_kwargs'] = ({'my_users': user_list})

    # Order tasks
    kwargs["queryset"] = self.object.tasks_set.order_by("task_order", "task_due")
    return kwargs

Everything is working as expected.

sdolemelipone commented 1 year ago

Great. You're welcome :-)