0

I have a simple foreign key relationship between two tables. I am able to save the parent, but am unable to save the child which has a foreign key to the parent. This is what my models look like:

class Product(models.Model):
    month_choices   = tuple((m,m) for m in calendar.month_abbr[1:])
    year_choices    = tuple((str(n), str(n)) for n in range(2004, datetime.now().year +2 ))
    id              = models.AutoField(primary_key = True)
    title           = models.CharField(max_length = 1024)
    product_type    = models.ForeignKey(ProductType)
    month           = models.CharField(max_length =3, choices=month_choices)
    year            = models.CharField(choices=year_choices, max_length = 4)
    project         = models.CharField(max_length = 15, null = True, blank = True)
    url             = models.URLField(null = True, blank = True)
    export_to_xsede = models.BooleanField()
    #def __str__(self):
    #    return str(self.id)
    class Meta:
        db_table = "product"

class ProductResource(models.Model):
    CHOICES             = (('A','A'),('B','B'),('C','C'),('D','D'),('E','E'))
    id                  = models.AutoField(primary_key = True)
    product             = models.ForeignKey(Product)
    resource            = models.CharField(choices=CHOICES, max_length = 15)

And my views:

class PublicationForm(forms.ModelForm):
        title = forms.CharField(widget=forms.TextInput(attrs={'size':'70'}),required=False)
        url = forms.CharField(widget=forms.TextInput(attrs={'size':'70'}),required=False)
        class Meta:
            model = Product
class ResourceForm(forms.ModelForm):
        resource = forms.MultipleChoiceField(choices=ProductResource.CHOICES, widget = forms.CheckboxSelectMultiple)
        class Meta:
            model = ProductResource

I save the parent:

saved_publication = publications_form.save()

but am unable to save the resource form:

resource_form = ResourceForm(request.POST, instance = saved_publication)
resource_form.product = saved_publication
resource_form.save()

When I print resource_form.errors, I get:

<ul class="errorlist"><li>product<ul class="errorlist"><li>This field is required.</li></ul></li></ul>

I have no idea why the foreign key is not getting set in this case.

tks
  • 55
  • 6
  • 1
    `saved_publication` is a `Product` object. Why are you setting it as the instance for the `ResourceForm`? Also, where is you view code? The code you have is just the forms code. – jproffitt Sep 26 '13 at 19:56
  • I may be wrong but I'm using that instance since it is a foreign key in Product Resource. My view is pretty large. Which parts of it would be helpful here? – tks Sep 26 '13 at 20:41
  • No, `instance` is an object that will pre-fill the form for editing. Since `ResourceForm` is a form for creating `ProductResource`s, it doesn't make sense to "prefill" it with a `Product` model. I am going to post an answer because I cannot fit everything here. – jproffitt Sep 26 '13 at 21:40

1 Answers1

1

I'm assuming you do not want to display the product field on the form, so you should exclude it from the form so the validation will pass:

class ResourceForm(forms.ModelForm):
    resource = forms.MultipleChoiceField(choices=ProductResource.CHOICES, widget = forms.CheckboxSelectMultiple)
    class Meta:
        model = ProductResource
        exclude = ['product']

Then in the view, just set the product manually after calling is_valid(). Just be sure to pass commit=False on the form.save() so that it will not actually save to the database until after you set the product. For example

...
saved_publication = publications_form.save()

resource_form = ResourceForm(request.POST)
if resource_form.is_valid():
    resource = resource_form.save(commit=False)
    resource.product = saved_publication
    resource.save()
jproffitt
  • 6,225
  • 30
  • 42
  • So this is what I have in my view now: resource_form = ResourceForm(request.POST) if resource_form.is_valid(): resource = resource_form.save(commit=False) resource.product = saved_publication resource.save() else: print resource_form.errors resource_form.errors gives me:
    • product
      • This field is required.
    – tks Sep 27 '13 at 14:06
  • did you put `exclude = ['product']` in your `ResourceForm`? – jproffitt Sep 27 '13 at 14:17
  • Ah yes, I did forget that. That error is gone now, but I am getting "
    • resource
      • Value u"[u'Staff']" is not a valid choice.
    " from resource_form.errors. Staff is one of the valid choices for the resource field.
    – tks Sep 27 '13 at 14:55
  • You set the resource field's `choices` to `(('A','A'),('B','B'),('C','C'),('D','D'),('E','E'))` `'Staff'` is not one of those. Am I missing something? – jproffitt Sep 27 '13 at 15:13
  • The A,B,C,D,E were placeholders for the values I have. So this is what choices is actually set to:CHOICES = (('Kraken','Kraken'),('Nautilus','Nautilus'),('Darter','Darter'),('KFS','KFS'),('KIDS','KIDS'),('Beacon','Beacon'),('Staff','Staff')) print resource_form and resource_form.errors are the following: – tks Sep 27 '13 at 17:07
  • print resource_form: `
    • Value u"[u'Staff']" is not a valid choice.
    • ...
    • `
    – tks Sep 27 '13 at 17:10
  • print resource_form.errors: `
    • resource
      • Value u"[u'Staff']" is not a valid choice.
    `
    – tks Sep 27 '13 at 17:11
  • Ah I see the problem now. Sorry I didn't catch it earlier. `resource` is just a `CharField`, so you cannot select multiple options for it. If you need to have multiple, you probably need to have a `ManyToManyField`. If you want to just be able to choose one, you could use a `RadioSelect` widget. (Or a regular `Select`). The field will also need to use a `ChoiceField` for the `resource` field itself. – jproffitt Sep 27 '13 at 18:07
  • No problem! I really appreciate the help! So would I need to tie the manytomany field to a model with a db table attached to the model? I'm trying to not create another table just to store the choice values. And yes, there can be one or more selected resource choices per product. – tks Sep 27 '13 at 19:22
  • Either that, or make your own comma separated field. Which actually wouldn't be too hard. – jproffitt Sep 27 '13 at 19:46
  • So I did create a model attached to a db table: `class ResourceType(models.Model): id = models.AutoField(primary_key = True) resource = models.CharField(max_length = 20) class Meta: db_table = "product_resource_type" class ProductResource(models.Model): id = models.AutoField(primary_key = True) product = models.ForeignKey(Product, to_field = "id", db_column = "product_id") resource = models.ManyToManyField(ResourceType) class Meta: db_table = "product_resource"` – tks Sep 27 '13 at 19:49
  • This is in my view: `class ResourceForm(forms.ModelForm): resource = forms.ModelMultipleChoiceField(queryset = ResourceType.objects.all(), widget = forms.CheckboxSelectMultiple(), required=True) class Meta: model = ProductResource exclude = ('product') ` and I save using: `resource_form = ResourceForm(request.POST) if resource_form.is_valid(): resource = resource_form.save(commit=False) resource.product = saved_publication resource.save() ` – tks Sep 27 '13 at 19:53
  • But the value of resource is saved as blank in the db in the product_resource table. Arg! – tks Sep 27 '13 at 19:54
  • Do you use south? There shouldn't be a `resource` column in the db. Instead, it should have a another table with a foreign key to `product_resource` and another foreign key to the `ResourceType` table. Also did you enter values in the `ResourceType` table? – jproffitt Sep 27 '13 at 20:18
  • No I don't use South. I don't think I get you. Wouldn't productResource and the table you describe have the same data, ie id, foreign key to product, and then foreign key to resource type table? And yes, I did enter values in the resource type table. :) I guess I don't have a great understanding of how m2m works in Django. – tks Sep 27 '13 at 20:37
  • Well then can you do syncdb? The intermediary table has to be created somehow. – jproffitt Sep 27 '13 at 20:46
  • I had already created a product_resource table by hand with the fields id, resource_id, and product_id. I then tried the save again but it was complaining about columns being absent after which I renamed the columns in the table to match what it was expecting. Now my product_resource table looks like `(id, productresource_id, resourcetype_id).` When I try save now, I get the error: `insert or update on table "product_resource" violates foreign key constraint "product_resource_product_id_id_fkey" DETAIL: Key (productresource_id)=(63) is not present in table "product"` – tks Sep 27 '13 at 21:10
  • I don't understand why you would want to manually create the tables. You know Django can do that for you right? – jproffitt Sep 27 '13 at 21:58