How to Query a ForeignKey field pointing to another ForeignKey field in Django

Viewed 42

I have a model (Letter) with a foreign key, pointing to another model (Company) with a foreign key. Below is a simple schema from models.py

from django.contrib.auth.models import User

class Company (models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, editable=False)
    date_created = models.DateField(default=timezone.now, null=True)
    company_name = models.CharField(max_length=100, null=True)

class Letter(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='letterhead_user', null=True)
    company = models.ForeignKey(Company, related_name = "company_letter", on_delete=models.CASCADE, null=True)
    subject = models.CharField(max_length=5000, null=True)
    body = models.TextField()

I have created a form where users can create Letters with the model through ModelForm.

class LetterForm(forms.ModelForm):

def __init__(self, *args, **kwargs): 
    super(LetterForm, self).__init__(*args, **kwargs)
    self.fields['company'].widget.attrs = {'class': 'input',}
    self.fields['subject'].widget.attrs = {'class': 'input', 'placeholder': 'RE: ...'}
    self.fields['body'].widget.attrs = {'class': 'textarea',}

class Meta:
    model = Letter
    fields = ('company', 'subject', 'body',)

The View:

def letter_form (request):

form = LetterForm() 
if request.method == 'POST':
    
    form = LetterForm(request.POST, request.FILES) 
    form.instance.user = request.user

    if form.is_valid():
        form.save() 
    return redirect('letter') 

Currently, when the user is presented with a form to create a Letter, on the Company field, all the companies from all the users appear. See the pic below:

Front end Form

I would like only the companies that the User has created to appear in the drop-down, not all companies from all users. Or to be able to select the first company that the User has created.

2 Answers

You can specify the logged in user in the form and filter accordingly. In the constructor of the form we thus limit the queryset with:

class LetterForm(forms.ModelForm):
    
    def __init__(self, *args, user=None, **kwargs): 
        super().__init__(*args, **kwargs)
        self.fields['company'].widget.attrs = {'class': 'input',}
        self.fields['subject'].widget.attrs = {'class': 'input', 'placeholder': 'RE: …'}
        self.fields['body'].widget.attrs = {'class': 'textarea',}
        if user is not None:
            self.fields['company'].queryset = Company.objects.filter(user=user)
    
    # …

and in the view, we then pass the logged in user to the constructor of the form:

from django.contrib.auth.decorators import login_required

@login_required
def letter_form(request):
    if request.method == 'POST':
        form = LetterForm(request.POST, request.FILES, user=request.user)
        form.instance.user = request.user
        if form.is_valid():
            form.save() 
            return redirect('letter') 
    else:
        form = LetterForm(user=request.user)
    # …

Note: You can limit views to a view to authenticated users with the @login_required decorator [Django-doc].


Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

If you use a ModelForm for Letterit may look like this:

class LetterForm(forms.ModelForm):
    class Meta:
        model = Letter
        fields = ["company", "subject", "body"]
    
    # you need to init the form with the right user instance
    def __init__(self, user=None, *args, **kwargs):
        # call the default __init__ behavior
        super().__init__(*args, **kwargs)
        # this is the trick, you will filter the companies queryset here
        if user:
             self.fields['company'].queryset = Company.objects.filter(user=user)

So you need to pass down the user in the form from your view:

something like:

def my_view(request):
    # assuming the user follow the standard Django user implementation
    # and you user is logged in
 
    form = LetterForm(request.POST or None, user=request.user)
    if form.is_valid():
        form.save()
        # then redirect the user to whatever success page 
        
    # render the form
    return render(request, "your_template.html", {"form": form})

Related