Django ForeignKey with through parameter

Viewed 668

Sorry the Title is wrong, I tried to change it but I can't. It doesn't mean 'through' parameter but 'to' parameter.

The following models make my code

class DocAide(models.Model):
    id = models.AutoField(primary_key=True)
    pulse = models.DecimalField('Pulse', max_digits=3, decimal_places=0, blank=True, null=True)
    weight = models.DecimalField('Weight (kg)', max_digits=3, decimal_places=0, blank=True, null=True)
    bp_sys = models.DecimalField('BP Sys', max_digits=3, decimal_places=0, blank=True, null=True)
    bp_dia = models.DecimalField('BP Dia', max_digits=3, decimal_places=0, blank=True, null=True)
    temperature = models.DecimalField('Temp. deg C', max_digits=2, decimal_places=1, blank=True, null=True)

    prescriptions = models.ForeignKey(PrescriptionLine, on_delete=models.CASCADE, blank=True, null=True)        

    scans = models.ManyToManyField(Scan, blank=True)
    tests = models.ManyToManyField(LabTest,  blank=True)
    date = models.DateField(editable=False, default=timezone.now)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
    created_by = models.ForeignKey(Doctor, on_delete=models.CASCADE, blank=True, null=True)
    no_charge = models.BooleanField('No Charge from Patient', default=False)
    doctors_notes = models.TextField('Patient is complaining about:', default='')
    part_to_scan = models.CharField(max_length=100, default='N/A', blank=True, null=True)
    part_to_xray = models.CharField(max_length=100, default='N/A', blank=True, null=True)
    note = models.TextField(max_length=100, default='')

and

class PrescriptionLine(models.Model):
    drug = models.ForeignKey(to=Drug, related_name='prescriptionlines', on_delete=models.CASCADE)

    morning = models.CharField(validators=[int_list_validator], max_length=3, default=0)
    midday = models.CharField(validators=[int_list_validator], max_length=3, default=0)
    evening = models.CharField(validators=[int_list_validator], max_length=3, default=0)
    night = models.CharField(validators=[int_list_validator], max_length=3, default=0)
    days = models.CharField(validators=[int_list_validator], max_length=3, default=0)
    tablets = models.CharField(validators=[int_list_validator], max_length=3, default=0)

My Forms looks like this:

class DocAideForm(forms.ModelForm):

    class Media:
        css = {'all': ('/static/admin/css/widgets.css',), }
        js = ('/admin/jsi18n/',)

    class Meta:
        model = DocAide
        fields = [  # 'no_charge',
                  'doctors_notes', 'scans', 'part_to_xray', 'part_to_scan', 'tests']

in view.py I have the inlineformset_factory

PrescriptionLineFormSet = inlineformset_factory(PrescriptionLine, DocAide, extra=1, fields=['drug', 'morning'])

With this construction the error comes "drug", "morning" unknown fields for DocAide.

3 Answers

To create form with inlilines, you must use the inlineformset_factory. You can check an example of how to use it in the official django documentation.

models.py:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)

views.py

from django.forms import inlineformset_factory
BookFormSet = inlineformset_factory(Author, Book, fields=('title',))
author = Author.objects.get(name='Mike Royko')
formset = BookFormSet(instance=author)

So to access the PrescriptionLine objects connected to a DocAide object you'd have something like this

docaide = DocAide.objects.get(id=5)
for line in docaide.prescriptionlines:
    line.drug = Drug.objects.get(id=6)

I want to point out that a DocAide object is connected to a list of PrescriptionLine objects. Even if a particular DocAide object only has one PrescriptionLine linked with it, the prescriptionlines attribute will return a list, or a queryset to be exact.

And I want to draw your attention to InlineFormsets. I'm not sure if this is what you're asking about, but its good to know about them anyways. InlineFormsets enable you to add/edit PrescriptionLines to a DocAide object in the same page. The example code I give below will only apply to the admin site.

class PrescriptionLineInline(admin.TabularInline):
    model = PrescriptionLine
    extra = 1

class DocAideAdmin(admin.ModelAdmin):
    inlines = [PrescriptionLineInline, ]

site.admin.register(DocAideAdmin)

This goes in the admin.py file and as I've said, it will only affect the admin site. This will create a section in the DocAide pages to add, edit, and delete PrescriptionLines directly.

To implement the same behavior in the front-end (outside the admin site), you'll have to edit your form class and use a formset_factory, or if you are working with a ModelForm (it seems like you are) you'd go with the easier inlineformset_factory.

If you do want to implement a formset in the front-end, I would suggest first implementing it in the admin site since it's easier, and you'll get a sense of how things fit together.

Here is the docs page about Inline formsets.

Have to considered Django package django-betterforms?

You could use the MultiModelForm class to combine both models into one form containing two separate model forms.

Example:

class DocAideForm(forms.ModelForm):

    class Media:
        css = {'all': ('/static/admin/css/widgets.css',), }
        js = ('/admin/jsi18n/',)

    class Meta:
        model = DocAide
        fields = [  # 'no_charge',
                  'doctors_notes', 'scans', 'part_to_xray', 'part_to_scan', 'tests']

class PrescriptionForm(forms.ModelForm):

    class Meta:
        model = PrescriptionLine
        fields =# other fields from model 

class CombinedForm(MultiModelForm):
    form_classes= {
        'DocAideForm':DocAideForm,
        'PrescriptionForm':PrescriptionForm ,
    }

Documentation is here. https://django-betterforms.readthedocs.io/en/latest/multiform.html

Related