首页 > 解决方案 > 限制用户可以上传的文件数量

问题描述

我有一个多文件上传,并希望每个用户限制为 3 个上传。我的问题是我需要知道user已经在数据库中创建了多少个文件以及他们当前正在上传多少个文件(他们可以一次上传多个文件,并且可以多次上传)。

我尝试了很多事情,包括:

创建一个验证器(验证器传递的是正在添加的实际文件,而不是 a model,所以我无法访问model来获取它的id调用if StudentUploadedFile.objects.filter(student_lesson_data=data.id).count() >= 4:)。

进行验证clean(self):clean一次只通过一个实例,并且数据库不会更新,直到所有文件都被清理,所以我可以计算数据库中已经存在的文件,但无法计算当前正在上传的文件数量)。

使用一种pre-save方法(如果在传递给我的方法的每个文件之间更新了数据库pre-save,它将起作用,但只有在所有上传的文件都通过我的pre-save方法后才更新数据库)。

我的post-save尝试:

@receiver(pre_save, sender=StudentUploadedFile)
def upload_file_pre_save(sender, instance, **kwargs):

    if StudentUploadedFile.objects.filter(student_lesson_data=instance.data.id).count() >= 4:
        raise ValidationError('Sorry, you cannot upload more than three files')

编辑:

模型.py

class StudentUploadedFile(models.Model):
    student_lesson_data = models.ForeignKey(StudentLessonData, related_name='student_uploaded_file', on_delete=models.CASCADE)
    student_file = models.FileField(upload_to='module_student_files/', default=None)

视图.py

class StudentUploadView(View):
    def get(self, request):
        files_list = StudentUploadedFile.objects.all()
        return render(self.request, 'users/modules.html', {'student_files': files_list})

    def post(self, request, *args, **kwargs):
        form = StudentUploadedFileForm(self.request.POST, self.request.FILES)
        form.instance.student_lesson_data_id = self.request.POST['student_lesson_data_id']

        if form.is_valid():
            uploaded_file = form.save()

            # pass uploaded_file data and username so new file can be added to students file list using ajax
            # lesson_id is used to output newly added file to corresponding newly_added_files div
            data = {'is_valid': True, 'username': request.user.username, 'file_id': uploaded_file.id, 'file_name': uploaded_file.filename(),
            'lesson_id': uploaded_file.student_lesson_data_id, 'file_path': str(uploaded_file.student_file)}
        else:
            data = {'is_valid': False}
        return JsonResponse(data)

模板.py

<form id='student_uploaded_file{{ item.instance.id }}'>
                                                {% csrf_token %}
                                                <a href="{% url 'download_student_uploaded_file' username=request.user.username file_path=item.instance.student_file %}" target='_blank'>{{ item.instance.filename }}</a>
                                                <a href="{% url 'delete_student_uploaded_file' username=request.user.username file_id=item.instance.id %}" class='delete' id='{{ item.instance.id }}'>Delete</a>
                                            </form>

js

$(function () {
    // open file explorer window
    $(".js-upload-photos").on('click', function(){
        // concatenates the id from the button pressed onto the end of fileupload class to call correct input element
        $("#fileupload" + this.id).click();
     });

    $('.fileupload_input').each(function() {
        $(this).fileupload({
            dataType: 'json',
            done: function(e, data) { // process response from server
            // add newly added files to students uploaded files list
            if (data.result.is_valid) {
                $("#newly_added_files" + data.result.lesson_id).prepend("<form id='student_uploaded_file" + data.result.file_id +
                "'><a href='/student_hub/" + data.result.username + "/download_student_uploaded_file/" +
                data.result.file_path + "' target='_blank'>" + data.result.file_name + "</a><a href='/student_hub/" + data.result.username +
                "/delete_student_uploaded_file/" + data.result.file_id + "/'  class='delete' id=" + data.result.file_id + ">Delete</a></form>")
            }
            }
        });
    });

更新:forms.py

class StudentUploadedFileForm(forms.ModelForm):
    student_file = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))

视图.py

class StudentUploadView(View):
    model = StudentUploadedFile
    max_files_per_lesson = 3

    def post(self, request, *args, **kwargs):
        lesson_data_id = request.POST['student_lesson_data_id']
        current_files_count = self.model.objects.filter(
            student_lesson_data_id=lesson_data_id
        ).count()
        avail = self.max_files_per_lesson - current_files_count
        file_list = request.FILES.getlist('student_file')
        print(len(file_list))
        if avail - len(file_list) < 0:
            return JsonResponse(data={
                'is_valid': False,
                'reason': f'Too many files: you can only upload {avail}.'
            })
        else:
            for f in file_list:
                print(f)
                
        data = {'test': True}
        return JsonResponse(data)

谢谢你。

标签: djangodjango-validation

解决方案


I guess that you can use multi file upload in Django hasn't trickled to the community yet. Excerpt:

If you want to upload multiple files using one form field, set the multiple HTML attribute of field’s widget:

# forms.py

from django import forms

class FileFieldForm(forms.Form):
    file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))

Your form and view structure is also very contrived, excluding fields from a form and then setting values injected via HTML form on the model instance. However, with the code shown, the model instance would never exist as the form has no pk field. Anyway - to focus on the problem that needs fixing...

In the form, request.FILES is now an array:

class StudentUploadView(View):
    model = StudentUploadedFile
    max_files_per_lesson = 3
    def post(request, *args, **kwargs):
        lesson_data_id = request.POST['student_lesson_data_id']
        current_files_count = self.model.objects.filter(
            student_lesson_data_id=lesson_data_id
        ).count()
        avail = self.max_files_per_lesson - current_files_count
        file_list = request.FILES.get_list('student_file')
        if avail - len(file_list) < 0:
            return JsonResponse(data={
                'is_valid': False,
                'reason': f'Too many files: you can only upload {avail}.'
            })
        else:
            # create one new instance of self.model for each file
            ...

Addressing comments: From an aesthetic perspective, you can do a lot with styling...

However, uploading async (separate POST requests), complicates validation and user experience a lot:

  • The first file can finish after the 2nd, so which are you going to deny if the count is >3.
  • Frontend validation is hackable, so you can't rely on it, but backend validation is partitioned into several requests, which from the user's point of view is one action.
  • But with files arriving out of order, some succeeding and some failing, how are you going to provide feedback to the user?
  • If 1, 3 and 4 arrive, but user cares more about 1, 2, 3 - user has to take several actions to correct the situation.

One post request:

  • There are no out of order arrivals
  • You can use a "everything fails or everything succeeds" approach, which is transparent to the end user and easy to correct.
  • It's likely that file array order is user preferred order, so even if you allow partial success, you're likely to do the right thing.

推荐阅读