1

Using the upload_to argument of FileField I want to store the file uploaded by the user in a folder named TestNameie the test name entered by the user. I've written the following code, but it creates a folder named "models.CharField(max_length=255)" instead. How can I fix this?

from django.core.validators import FileExtensionValidator
from django.db import models
from django.contrib.auth.models import User


class TestInfo(models.Model):
    TestName = models.CharField(max_length=255)
    MaxMarks = models.IntegerField()
    TimeDuration = models.IntegerField()
    PosMarks = models.IntegerField()
    NegMarks = models.IntegerField()
    InputTextFile = models.FileField(upload_to='Tests/{}/'.format(TestName),\
    validators[FileExtensionValidator(allowed_extensions['txt'])],blank=False)

    def __str__(self):
        return self.TestName
Kiran
  • 55
  • 1
  • 7
  • Possible duplicate of [Dynamic File Path in Django](https://stackoverflow.com/questions/5135556/dynamic-file-path-in-django) – Sam Feb 25 '19 at 15:40

1 Answers1

1

I think you just missed an additional function which could help you in this. I have not had time to test this particularly now but you are looking for something like this:

from django.core.validators import FileExtensionValidator
from django.db import models
from django.contrib.auth.models import User

def content_file_name(instance, filename):
    return "Tests/{folder}/{file}".format(folder=instance.TestName, file=filename)

class TestInfo(models.Model):
    TestName = models.CharField(max_length=255)
    MaxMarks = models.IntegerField()
    TimeDuration = models.IntegerField()
    PosMarks = models.IntegerField()
    NegMarks = models.IntegerField()
    InputTextFile = models.FileField(upload_to=content_file_name)
    validators[FileExtensionValidator(allowed_extensions['txt'])],blank=False)

    def __str__(self):
        return self.TestName

UPDATE - ON USING THE USERNAME AS FOLDER NAME TO SAVE UPLOADED FILE

If you want to add the username as the subfolder name where the user's uploads will be saved, into different subfolders by usernames. And at the same time you will keep the uploader's user name also in your model, then you should formulate your model a bit more like below. I added an extra field to your model as Uploader_info. It just gets the username at uploads, so it cannot be edited by the uploader user, it is just given always at uploads from your user table via request data.

in models.py:

from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError

def content_file_name(instance, filename):
    return "Tests/{subfolder}/{folder}/{file}".format(subfolder=instance.Uploader_info, folder=instance.TestName, file=filename)

def validate_file_extension(value):
    if value.file.content_type != 'text/plain':
        raise ValidationError('The uploaded file must be a text file')

class TestInfo(models.Model):
    Uploader_info = models.CharField(max_length=100, editable=False, null = True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='testinfos', on_delete=models.CASCADE, null=True)
    TestName = models.CharField(max_length=255)
    MaxMarks = models.IntegerField()
    TimeDuration = models.IntegerField()
    PosMarks = models.IntegerField()
    NegMarks = models.IntegerField()
    InputTextFile = models.FileField(upload_to=content_file_name, blank=False, validators=[validate_file_extension])        

    def __str__(self):
        template = '{0.TestName} {0.Uploader_info}'
        return template.format(self)

Do not forget to migrate.

Important! Register the model in the admin.py too with a short added function which will give the uploader's username to the records:

in admin.py:

from .models import TestInfo

class TestInfoAdmin(admin.ModelAdmin):

    def save_model(self, request, obj, form, change):
        if not change:
            obj.Uploader_info = request.user.username
        obj.save()

admin.site.register(TestInfo, TestInfoAdmin)

Your current view name is post for the Form submission, which has to be reformulated a bit like this:

in views,py

@login_required
def post(request):
    if request.method == 'POST':
        # create a form instance and populate it with data from the request:
        form = TestForm(request.POST, request.FILES)
        # check whether it's valid:
        if form.is_valid():
            fulltest = form.save(commit=False)
            fulltest.user = request.user
            fulltest.Uploader_info = request.user
            TestName = form.cleaned_data.get('TestName')
            File = form.cleaned_data.get('InputTextFile')
            fulltest.save()
            messages.success(request, 'Test {} Posted successfully'.format(TestName))
            return redirect('Test-Making')
        else:
            parameters = {
              'form': form,                   
              'error': form.errors.as_ul()
            }
            return render(request, 'Test/post.html', parameters)

    # if a GET (or any other method) we'll create a blank form
    else:
        form = TestForm()
        return render(request, 'Test/post.html', {'form': form})

and your Form in forms.py is simply:

class TestForm(forms.ModelForm):
    class Meta:
        model = TestInfo
        fields = ('TestName', 'MaxMarks', 'TimeDuration', 'PosMarks', 'NegMarks', 'InputTextFile')

And that's about it. If your Form is OK then the above will work as I tested that successfully.

Zollie
  • 1,171
  • 7
  • 14
  • Thank you! it worked like a charm! what if I want username of the current user logged in in place of "Tests"? ie path: username/TestName/filename – Kiran Feb 25 '19 at 17:10
  • @Kiran - it's just a bit more tricky. Only later or tomorrow I'll have time to update for that if you do not solve that until. There are few different methods to solve this. – Zollie Feb 25 '19 at 17:45
  • @Kiran, I updated my answer with the username subfolder version of the code. I hope it will work for you too without problem, as it worked for me. Take care, my friend! – Zollie Feb 26 '19 at 10:21
  • I've tried implementing your solution. and for some reason the value of `Uploader_info` is always equal to None. what could be the reason? – Kiran Feb 27 '19 at 15:11
  • @Kiran - I updated my answer. I added a user field to the Model and reformulated your view to get the user info and user id for the submitted form. So please use that updated model and view and give me some feedback. I tested this again and it works now from front-end submission too. – Zollie Feb 27 '19 at 20:07
  • @Kiran - And i just placed a better validation in the model which checks not only the extension of the file but the format of the file too. I put an error redirect part in the view too if there is error in the form. – Zollie Feb 28 '19 at 07:07
  • Thank you again! yes it worked. However if the uploaded file is not a 'txt' file it doesn't show the validation error mentioned in the validate_file_extension function. – Kiran Mar 01 '19 at 15:41
  • @Kiran - I just tested that again from front-end upload and the file type checker worked for me too, I wanted to upload a PHP file and a PDF and got back the error. Are you sure that everything you set OK in that model field? Have you imported django validationerror into the model on the top: `from django.core.exceptions import ValidationError` – Zollie Mar 01 '19 at 16:13
  • Yes everything in the model field looks fine, also I have imported that package. – Kiran Mar 03 '19 at 13:43
  • @Kiran - You use Crispy form in your template and that can cause that the error message does not appear. I tested this without crispy. So you need to check a bit more on how crispy shows error messages. I'll check that too a bit later, what is missing from your form or template. – Zollie Mar 03 '19 at 14:43
  • @Kiran - Maybe you can try to change loading the crispy form tags like this `{% load crispy_forms_tags %}` which you already did, and load the form like this: `{{ form|as_crispy_errors }}` in your template. – Zollie Mar 03 '19 at 17:13
  • I've tried that too. but no luck, it still doesn't work. – Kiran Mar 05 '19 at 18:11
  • @Kiran - check the pull request on github. – Zollie Mar 06 '19 at 19:21
  • can you help me with this problem https://stackoverflow.com/questions/57393043/how-to-change-the-value-of-model-field-when-button-is-clicked-in-django – Kiran Aug 07 '19 at 12:04