1

I'm trying to use CBVs as much as possible and want to pre-populate data in a ModelForm based on a generic.CreateView with some data passed in via URL.

I might be over thinking or confusing myself. All code abridged for legibility

We have an inventory system with PartNumbers (abstractions), Carriers (actual instances of PartNumbers with location, serial and quantity numbers) and Movements for recording when items are extracted from the inventory, how much is taken and what Carrier it came from.

I would like to have the "extract inventory" link on the PartNumber detail page, and then have the available carriers ( pn.carrier_set.all() ) auto filled into the FK drop down on the MovementForm.

models.py

class PartNumber(models.Model):
  name = models.CharField("Description", max_length=100)
  supplier_part_number = models.CharField(max_length=30, unique=True)
  slug = models.SlugField(max_length=40, unique=True)

class Carrier(models.Model):
  part_numbers = models.ForeignKey(PartNumber)
  slug = models.SlugField(max_length=10, unique=True, blank=True, editable=False)
  location = models.ForeignKey(Location)
  serial_number = models.CharField(max_length=45, unique=True, null=True, blank=True)
  qty_at_new = models.IntegerField()
  qty_current = models.IntegerField()

class Movement(models.Model):
  carrier = models.ForeignKey(Carrier)
  date = models.DateField(default=timezone.now())
  qty = models.IntegerField()

I have been playing around with get_initial() and get_form_kwargs() without success:

In urls.py I collect the PartNumber via url as pn_slug

url(r'^partnumber/(?P<pn_slug>[-\w]+)/extract/$', views.MovementCreate.as_view(), name='pn_extract'),

forms.py is generic

class MovementForm(forms.ModelForm):
  class Meta:
    model = Movement

views.py

class MovementCreate(generic.CreateView):
  form_class = MovementForm
  model = Movement

  def get_form_kwargs(self):
    kwargs = super(MovementCreate, self).get_form_kwargs()
    kwargs['pn_slug'] = self.request.POST.get("pn_slug")
    return kwargs

  # here we get the appropriate part and carrier and.
  # return it in the form
  def get_initial(self):
    initial = super(MovementCreate, self).get_initial()
    # this didn't work, hence using get_form_kwargs
    #pn = PartNumber.objects.get(slug=self.request.POST.get("pn_slug"))
    pn = PartNumber.objects.get(slug=self[pn_slug])
    carriers = pn.carrier_set.all()
    initial['carrier'] = carriers
    return initial

As it stands, I'm getting "global name 'pn_slug' is not defined" errors - but I doubt that error accurately reflects what I have done wrong.

I have been using these posts as rough guidelines:

How to subclass django's generic CreateView with initial data?

How do I use CreateView with a ModelForm

Community
  • 1
  • 1
datakid
  • 2,293
  • 5
  • 23
  • 35
  • I am a bit confused with your process. Is this creation form for a `Movement` or a form to create `PartNumber`? You are setting the model to `Movement`, but then you are setting the initial data as a `PartNumber`. (`pn = PartNumber.objects.get(slug=self[pn_slug])`) – jproffitt Sep 25 '13 at 01:53
  • Ok, two things. --- 1. I'm creating a Movement from a Part. From the Part, we get the Carrier. In the MovementCreateView we want the Carrier FK to be a sub set of all the Carriers (based on the PartNumber). So we get the part object from slug, then we get the list of available carriers by pn.carrier_set.all() then we send that list of carriers to the ModelForm for the Movement. --- 2. I have a subset of this working - I needed to use pn = PartNumber.objects.get(slug=self.kwargs["pn_slug"]) in get_initial() – datakid Sep 25 '13 at 02:30
  • So, essentially I'm looking for a way to change the queryset used by an FK in a form. – datakid Sep 25 '13 at 03:06

1 Answers1

1

If I understand you correctly from our comments, all you need is just to change the queryset of the MovementForm's carrier field to set the available options. In that case, I would use get_initial nor get_form_kwargs at all. Instead, I would do it in get_form:

def get_form(self, *args, **kwargs):
    form = super(MovementCreate, self).get_form(*args, **kwargs)

    pn = PartNumber.objects.get(slug=self.kwargs['pn_slug'])
    carriers = pn.carrier_set.all()

    form.fields['carrier'].queryset = carriers
    return form

Another way to do it would be to use get_form_kwargs:

def get_form_kwargs(self):
    kwargs = super(MovementCreate, self).get_form_kwargs()
    kwargs['pn_slug'] = self.kwargs.get("pn_slug")
    return kwargs

Then, in the form's __init__, set the queryset:

class MovementForm(forms.ModelForm):
    class Meta:
    model = Movement

    def __init__(self, *args, **kwargs):
        pn_slug = kwargs.pop('pn_slug')
        super(MovementForm, self).__init__(*args, **kwargs)

        pn = PartNumber.objects.get(slug=pn_slug)
        carriers = pn.carrier_set.all()

        self.fields['carrier'].queryset = carriers

Personally, I would prefer the first method as it is less code.

jproffitt
  • 6,225
  • 30
  • 42