Django FilePathField throwing a TypeError

When a candidate submits a job application, I want the CV field on the application model to point to the CV that the candidate has saved on their profile.

models.py

def cv_path(instance):
    return os.path.join(settings.MEDIA_ROOT, 'candidates/user_{0}'.format(instance.candidate.user.id))


def user_directory_path(instance, filename):
    return 'candidates/user_{0}/{1}'.format(instance.user.id, filename)


class Candidate(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
    cv = models.FileField(upload_to=user_directory_path, default="CV to be added")
    
    def __str__(self):
        return f"{self.user.first_name} {self.user.last_name}"


class Application(models.Model):
    candidate = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    job = models.ForeignKey('Vacancy', on_delete=models.CASCADE)
    cv = models.FilePathField(path=cv_path, default=None)
    cover_letter = models.TextField(default=None, validators=[
        MinLengthValidator(0),
        MaxLengthValidator(2000)
    ])
    submitted = models.DateTimeField(auto_now_add=True)
    is_succesful = models.BooleanField(default=False)

Right now, if I try to add an application from the admin site I get this TypeError:

TypeError at /admin/recruit/application/add/
cv_path() missing 1 required positional argument: 'instance'

However if I change the cv_path to this:

def cv_path():
    return os.path.join(settings.MEDIA_ROOT, 'candidates/user_2')

I do not get any error and the cv field gives me a dropdown of all the CV’s saved for the user with an id of 2 but of course I cannot hardcode this.

How can I fix this error?

I don’t have a specific solution for this, but since you’re looking to generate different lists depending on user, this is something that needs to be changed at runtime. As a result, my first reaction is that you’re going to need to override some method within your ModelAdmin class - possibly get_form. That gives you access to the current request object from which you can retrieve the User.id to customize that field value.

Very rough idea:

class ApplicationAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        file_path = cv_path(request.user.id)
        form.fields['cv'] = models.FilePathField(path=file_path, default=None)
        return form