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

Time:04-05

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, instance=request.user)
        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" enctype="multipart/form-data">
    {% 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

UPDATE:

urls.py

urlpatterns = [
    path('register', RegisterAPIHTML.as_view(), name='register'),
    path('login', LoginAPIHTML.as_view(), name='login'),
    path('user', UserAPI.as_view(), name='user'),
    path('logout', LogoutAPI.as_view(), name='logout'),
]   static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

settings.py

STATIC_ROOT  =   os.path.join(BASE_DIR, 'staticfiles')

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

MEDIA_ROOT = os.path.join(BASE_DIR, 'data/')
MEDIA_URL = '/media/'

CodePudding user response:

I think your form is not saving anything because the line with form.is_valid() returns False.

You won't see anything in the form.errors because that attribute only shows field errors, not non-field errors. The non-field error in this case is that the required field user is missing. This is because you haven't specified it in the model form (also why it gets placed as a non-field error).

Also, you're not rendering the non-field errors in the HTML which is why you don't see them in your page after the post submission. I suggest using crispy-forms package (here's the link). It handles things like form rendering out of the box.

Moreover, specifying argument ìnstance=request.user is incorrect because if you have a DetailForm the instance should be a model instance of the Detail model, not User model. Keep in mind, instance kwarg is only necessary if you want to use this form for updating.

I suggest an implementation similar to the following:

forms.py

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

        widgets = {
            # I'm guessing you don't want to see the User field
            'user': HiddenInput()
        }

Relevant part of views.py

def get(self, request):
    form = DetailForm()

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

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


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

To make this a complete answer, I will include a comment from @raphael which fixed the first issue of your code:

Make sure your form tag is as follows: <form method="post" enctype="multipart/form-data">

Some resources to follow:

  • Related