【问题标题】:Limit Amount Of Files A User Can Upload限制用户可以上传的文件数量
【发布时间】:2020-07-11 05:10:38
【问题描述】:

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

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

创建一个validator(验证器传递的是要添加的实际文件,而不是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')

编辑:

models.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)

views.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}))

view.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)

谢谢。

【问题讨论】:

  • 为什么不按 user_id 来计算呢?统计用户过滤的上传数据
  • 您的 upload_file_pre_save 函数指的是在 data_id 字段上过滤的 UploadedFile 模型。您的 models.py 声明了一个带有 student_lesson_data 字段的 StudentUploadedFile 模型。这些是一样的吗?还不清楚StudentUploadedFile 是如何与您的用户相关联的。 StudentLessonData 是自定义用户吗?
  • 对不起,我忘记了我简化了我最初发布的代码以使其更易于理解。我把这些东西修好了,是的,它们是一样的。对于每节课,都会注册一个Student,他们有一个StudentLessonData 对象,其中包含特定于该Student/课程的数据。 StudentUploadedFileStudentLessonData 具有一对多关系(一个 StudentLessonData 可以有多个 StudentUploadedFiles)。
  • 您的问题表明您希望“将用户限制为每个用户 3 次上传”。是每个Student 3 次上传,还是每个StudentLessonData 3 次上传?
  • StudentModuleData 是从哪里来的?你之前没有提到过那个模型。

标签: django django-validation


【解决方案1】:

我猜你可以在 Django 中使用 multi file upload 还没有进入社区。摘录:

如果要使用一个表单字段上传多个文件,请设置字段小部件的 multiple HTML 属性:

# forms.py

from django import forms

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

您的表单和视图结构也非常做作,从表单中排除字段,然后在模型实例上设置通过 HTML 表单注入的值。但是,使用显示的代码,模型实例永远不会存在,因为表单没有 pk 字段。 无论如何 - 专注于需要解决的问题......

在表单中,request.FILES 现在是一个数组:

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
            ...

寻址 cmets: 从美学的角度来看,你可以做很多事情with styling...

但是,上传异步(单独的 POST 请求)会使验证和用户体验变得非常复杂:

  • 第一个文件可以在第二个之后完成,所以如果计数>3,你会拒绝哪个。
  • 前端验证是可破解的,因此您不能依赖它,但后端验证分为多个请求,从用户的角度来看,这是一个操作。
  • 但是由于文件乱序到达,有些成功,有些失败,您将如何向用户提供反馈?
  • 如果 1、3 和 4 到达,但用户更关心 1、2、3 - 用户必须 采取一些措施来纠正这种情况。

一个帖子请求:

  • 没有乱序到达
  • 您可以使用“一切都失败或一切都成功”的方法,这种方法对最终用户透明且易于纠正。
  • 文件数组顺序很可能是用户首选的顺序,因此即使您允许部分成功,您也可能会做正确的事情。

【讨论】:

  • 谢谢。我尝试了您的建议(我更新了上面的代码),但是当我在 if avail - len(file_list) &lt; 0: 之前 print(len(file_list)) 它打印 1 并重复正在上传的文件数量
  • 你抛弃了 JQuery 吗?您应该发送 一个 POST 请求,而不是每个文件一个。
  • 哦,好的。我希望保留 jquery,因为我只想有一个按钮(而不是“选择文件”按钮和“上传”按钮,jquery 只有一个“选择文件”按钮并自动上传文件而无需第二次按下按钮)。它还将文件添加到用户的上传文件列表中,而无需刷新页面。但是,如果它使事情变得复杂,请放弃 jquery。谢谢
【解决方案2】:

我尝试过使用 PyPi 包,它可以完美运行。我将在这里冒险并假设您愿意编辑包代码以修复由于兼容性问题而遇到的任何错误,因为大多数长时间未更新的包可能会遇到它们。

为了解决限制用户可以上传的文件数量的问题,django-multiuploader 包将有很大的帮助,并且会比您要求的更多。是的,它使用 JQuery 表单来上传多个文件。

如何使用?

安装和使用前步骤

安装

pip install django-multiuploader
python3 manage.py syncdb
python3 manage.py migrate multiuploader

在您的settings.py 文件中:

MULTIUPLOADER_FILES_FOLDER = ‘multiuploader’ # - media location where to store files

MULTIUPLOADER_FILE_EXPIRATION_TIME = 3600  # - time, when the file is expired (and it can be cleaned with clean_files command).

MULTIUPLOADER_FORMS_SETTINGS =

{
'default': {
    'FILE_TYPES' : ["txt","zip","jpg","jpeg","flv","png"],
    'CONTENT_TYPES' : [
            'image/jpeg',
            'image/png',
            'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'application/vnd.ms-excel',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'application/vnd.ms-powerpoint',
            'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'application/vnd.oasis.opendocument.text',
            'application/vnd.oasis.opendocument.spreadsheet',
            'application/vnd.oasis.opendocument.presentation',
            'text/plain',
            'text/rtf',
                ],
    'MAX_FILE_SIZE': 10485760,
    'MAX_FILE_NUMBER':5,
    'AUTO_UPLOAD': True,
},
'images':{
    'FILE_TYPES' : ['jpg', 'jpeg', 'png', 'gif', 'svg', 'bmp', 'tiff', 'ico' ],
    'CONTENT_TYPES' : [
        'image/gif',
        'image/jpeg',
        'image/pjpeg',
        'image/png',
        'image/svg+xml',
        'image/tiff',
        'image/vnd.microsoft.icon',
        'image/vnd.wap.wbmp',
        ],
    'MAX_FILE_SIZE': 10485760,
    'MAX_FILE_NUMBER':5,
    'AUTO_UPLOAD': True,
},
'video':{
    'FILE_TYPES' : ['flv', 'mpg', 'mpeg', 'mp4' ,'avi', 'mkv', 'ogg', 'wmv', 'mov', 'webm' ],
    'CONTENT_TYPES' : [
        'video/mpeg',
        'video/mp4',
        'video/ogg',
        'video/quicktime',
        'video/webm',
        'video/x-ms-wmv',
        'video/x-flv',
        ],
    'MAX_FILE_SIZE': 10485760,
    'MAX_FILE_NUMBER':5,
    'AUTO_UPLOAD': True,
},
'audio':{
    'FILE_TYPES' : ['mp3', 'mp4', 'ogg', 'wma', 'wax', 'wav', 'webm' ],
    'CONTENT_TYPES' : [
        'audio/basic',
        'audio/L24',
        'audio/mp4',
        'audio/mpeg',
        'audio/ogg',
        'audio/vorbis',
        'audio/x-ms-wma',
        'audio/x-ms-wax',
        'audio/vnd.rn-realaudio',
        'audio/vnd.wave',
        'audio/webm'
        ],
    'MAX_FILE_SIZE': 10485760,
    'MAX_FILE_NUMBER':5,
    'AUTO_UPLOAD': True,
}}

请注意MAX_FILE_NUMBER,就在他们的谎言中,您的问题的答案。安装后查看源代码,如果需要,请尝试自行实施。可能会很有趣。

请参阅更多说明: django-multiuploader package on pypi

【讨论】:

  • 谢谢。我正在看这个,我会告诉你我是怎么走的
【解决方案3】:

所以它的要点是您使用 jQuery .each 通过 AJAX 上传图像。对 Django 视图的每个 POST 请求都是一个文件上传,但可能同时有多个请求。

试试这个:

forms.py:

class StudentUploadedFileForm(forms.ModelForm):

    class Meta:
        model = StudentUploadedFile
        fields = ('student_file', )

    def __init__(self, *args, **kwargs):
        """Accept a 'student_lesson_data' parameter."""
        self._student_lesson_data = kwargs.pop('student_lesson_data', None)
        super(StudentUploadedFileForm, self).__init__(*args, **kwargs)

    def clean(self):
        """
        Ensure that the total number of student_uploaded_file instances that
        are linked to the student_lesson_data parameter are within limits."""
        cleaned_data = super().clean()
        filecount = self._student_lesson_data.student_uploaded_file.count()
        if filecount >= 3:
            raise forms.ValidationError("Sorry, you cannot upload more than three files")
        return cleaned_data

views.py:

class StudentUploadView(View):
    def get(self, request):
        # stuff ...

    def post(self, request, *args, **kwargs):
        sld_id = request.POST.get('student_lesson_data_id', None)
        student_lesson_data = StudentLessonData.objects.get(id=sld_id)
        form = StudentUploadedFileForm(
            request.POST,
            request.FILES,
            student_lesson_data=student_lesson_data
        )

        if form.is_valid():
            uploaded_file = form.save()
            # other stuff ...

【讨论】:

  • 非常感谢您的回复,但是这还不是很清楚。我将print 语句放在__init__clean 函数中,并看到它执行__init__,然后是clean,用于在多文件上传中添加的每个文件(如果我ctrl-click 3个文件,它会执行3次)。但是,如果我在clean 方法中的if 语句之前print(filecount),它会在上传当前文件之前打印数据库中的文件数,并且不会随着每个文件而增加。
  • 没问题。根据您的测试,我认为您最好的选择是更新您的 javascript,以便在一个 POST 中提交所有图像。
  • 好的,不用担心。稍后我有时间会看一下 javascript。如果我在一篇文章中提交它,我猜你的解决方案会成功。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-19
  • 2020-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多