How do I upload a file in Django OneToOne Model's FileField using form?

Hi there,

I have one model Detail which has OneToOne relation with default User Model. I have a field FileField in my Detail model, where I want to upload the files using forms from frontend/templates.

I have been working around it a lot but I am not getting it done. I need help, please.

My models.py is:

from django.db import models
from django.contrib.auth.models import User

class Detail(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    file = models.FileField(verbose_name="CSV File", upload_to='csv_files')
    file_desc = models.TextField("CSV File Description")

    def __str__(self):
        return ("{} ({} {})".format(self.user.email, self.user.first_name, self.user.last_name))

My forms.py is:

from django.forms import ModelForm
from .models import Detail

class DetailForm(ModelForm):
    class Meta:
        model = Detail
        fields = ['file', 'file_desc']

My views.py is:

from django.views import View

class UserAPI(View):
    template_name = 'accounts/user.html'

    def get(self, request):
        form = DetailForm(instance=request.user)

        context = {'form': form}
        return render(request, self.template_name, context)

    def post(self, request):
        form = DetailForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect('user')

        context = {'form': form}
        return render(request, self.template_name, context)

and my user.html (template) is:

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
</form>

Every time I go to localhost:8000/user and fill the form and click on Submit Button, it gives me following error on frontend:
No File Chosen
and also the following statement appears above the File Selection Button:
This field is required.

I shall appreciate for the help. Thanks

You’re not supplying the instance parameter when you’re building the form on the POST. There’s no way for this form to be associated with a User. (The form does not have the user field in it, so that field needs to come from “somewhere” for the model to be complete.)

Also see the docs at File Uploads | Django documentation | Django for the enctype setting needing to be made in the form template.

1 Like

@KenWhitesell I did what you said, now my `user.html looks like below:

<form method="post" enctype="multipart/form-data"></form>>
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
</form>

and my views.py looks like below:

clas UserAPI(View):
    template_name = 'accounts/user.html'

    def get(self, request):
        form = DetailForm(instance=request.user)

        context = {'form': form}
        return render(request, self.template_name, context)

    def post(self, request):
        form = DetailForm(request.POST, request.FILES, instance=request.user)
        if form.is_valid():
            form.save()
            return redirect('user')

        context = {'form': form}
        return render(request, self.template_name, context)

Now, it does not show errors, but nothing else happens. Actually earlier I was debugging the code and it was working fine till this line form = DetailForm(request.POST, request.FILES, instance=request.user), but now it is not even executing the same line.

You’re closing your form (</form>) before the form itself.

1 Like

OK that was some serious hidden mistake. Thanks @KenWhitesell for pointing it out. By the way, I did some changings after reading the documentation. I added / at the end of upload_to='csv_files' in Detailmodel. and from some internet search, it was recommended to changeinstance=request.usertoinitial={‘user’: request.user}, and also added else: print(form.errorsafter theif form.is_valid` statement to view the errors.

It supposed to be working, but now I am getting the following error:
django.db.utils.IntegrityError: NOT NULL constraint failed: accounts_detail.user_id

I disagree. You typically use initial for a “regular” (non-model) form, but instance when you’re working with a Model Form. Also, user is not a field in the form, so setting an initial value for it is meaningless.

OK @KenWhitesell , I changed it back to instance.request.user, but now it is showing the same error No file chosen, however the error This field is required is gone. And I added a print statement print("Success") after the if form.is_valid() and it got printed in the console, that means form is validated successfully, but form.save() is not being worked correctly.

Can you post your current form and view with your most recent changes / corrections?

Yes @KenWhitesell , below is my current models.py:

class Detail(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    file = models.FileField(verbose_name="CSV File", upload_to='csv_files/')
    file_desc = models.TextField("CSV File Description")

    def __str__(self):
        return ("{} ({} {})".format(self.user.email, self.user.first_name, self.user.last_name))

below is my current forms.py:

class DetailForm(ModelForm):
    class Meta:
        model = Detail
        fields = ['file', 'file_desc']

below is my current views.py:

class UserAPI(View)
    template_name = 'accounts/user.html'

    def get(self, request):
        form = DetailForm(instance=request.user)

        context = {'form': form}
        return render(request, self.template_name, context)

    def post(self, request):
        form = DetailForm(request.POST, request.FILES, instance=request.user)

        if form.is_valid():
            form.save()
            return redirect('user')
        else:
            print(form.errors)

        context = {'form': form}
        return render(request, self.template_name, context)

and below is my current user.html::

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
</form>

First, the instance parameter should be the instance of Detail that is being modified by the data from this form, not of request.user.

Also, you should create the instance of detail before trying to submit this form which is trying to update the Detail instance.

So for example, your get method could start like this:
detail, created = Detail.objects.get_or_create(user=request.user)
form = DetailForm(instance=detail)

Your post method doesn’t need to do the get_or_create, it could just do a get, because the get method will create the Detail object if it needs to be created.

1 Like

Hi @KenWhitesell I just updated my get() and post() method as you said, that first to create the Detail instance then pass it to the instance parameter . So my new code of views.py looks like below:

class UserAPI(View):
    template_name = 'accounts/user.html'

    def get(self, request):
        detail, created = Detail.objects.get_or_create(user=request.user)
        form = DetailForm(instance=detail)

        context = {'form': form}
        return render(request, self.template_name, context)

    def post(self, request):
        detail, created = Detail.objects.get(user=request.user)
        form = DetailForm(request.POST, request.FILES, instance=detail)
        if form.is_valid():
            form.save()
            return redirect('user')
        else:
            print(form.errors)

        context = {'form': form}
        return render(request, self.template_name, context)

But, now I am getting the below error:
accounts.models.Detail.DoesNotExist: Detail matching query does not exist.

The full details of this Traceback error is below:

Traceback Switch to copy-and-paste view
C:\Users\Khubaib Khawar\AppData\Local\Programs\Python\Python310\lib\site-packages\django\core\handlers\exception.py, line 55, in inner
                response = get_response(request) …
Local vars
C:\Users\Khubaib Khawar\AppData\Local\Programs\Python\Python310\lib\site-packages\django\core\handlers\base.py, line 197, in _get_response
                response = wrapped_callback(request, *callback_args, **callback_kwargs) …
Local vars
C:\Users\Khubaib Khawar\AppData\Local\Programs\Python\Python310\lib\site-packages\django\views\generic\base.py, line 84, in view
            return self.dispatch(request, *args, **kwargs) …
Local vars
C:\Users\Khubaib Khawar\AppData\Local\Programs\Python\Python310\lib\site-packages\django\views\generic\base.py, line 119, in dispatch
        return handler(request, *args, **kwargs) …
Local vars
C:\Users\Khubaib Khawar\Downloads\Meistery\Round2\backend_dev_trial_ass_r2\accounts\views.py, line 270, in post
        detail, created = Detail.objects.get(user=request.user) …
Local vars
C:\Users\Khubaib Khawar\AppData\Local\Programs\Python\Python310\lib\site-packages\django\db\models\manager.py, line 85, in manager_method
                return getattr(self.get_queryset(), name)(*args, **kwargs) …
Local vars
C:\Users\Khubaib Khawar\AppData\Local\Programs\Python\Python310\lib\site-packages\django\db\models\query.py, line 496, in get
            raise self.model.DoesNotExist( …
Local vars

Hi @KenWhitesell I just figured out that I did not refresh the page after saving the views.py() and directly entered the data and submitted, which in result, only executed the post() method of my UserAPI view, and as get() was not executed, so Detail instance was not created and that’s why I was having the error I mentioned in my above reply.

Furthermore, I also figured out that only get_or_create() method returns a tuple, so I had to use detail, created only in the get() method, and just the detail in post() method.

So, finally it is working perfect. Thank you so much for the time you gave. You have always been a great help in my Django projects.

Is there a possibility that I get your email address or contact number? Thanks again.

1 Like

What’s the appropriate syntax for a get query?

Hey, please read my latest reply.

Yep, saw it - both messages were posted at about the same time.

Yes, thanks again. Did you read my request at the end of my reply?

Yes I did. I’m going to pass on that though, sorry.

No worries. Have a good day!