Ограничение количества файлов, которые может загрузить пользователь - PullRequest
8 голосов
/ 11 июля 2020

У меня есть загрузка нескольких файлов, и я хочу ограничить количество загрузок для каждого пользователя 3. Моя проблема в том, что мне нужно знать, сколько файлов user уже создал в БД и сколько они загружают в настоящее время (они могут загружать несколько файлов одновременно и могут загружать несколько раз).

Я пробовал много вещей, в том числе:

Создание валидатора (валидатору был передан фактический добавляемый файл, а не model, поэтому я не мог получить доступ к model чтобы получить его id для вызова if StudentUploadedFile.objects.filter(student_lesson_data=data.id).count() >= 4:).

Выполнение проверки в clean(self): (clean передается только по одному экземпляру за раз, и БД не обновляется до тех пор, пока все файлы не будут очищены , поэтому я мог подсчитать количество файлов, уже находящихся в БД, но не мог подсчитать, сколько в настоящее время загружается). 1019 * метод, он будет работать, но БД обновляется только после того, как все загружаемые файлы прошли через мой 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)

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

Спасибо.

Ответы [ 3 ]

1 голос
/ 17 июля 2020

Я пробовал использовать пакет PyPi, и он работает безупречно. Я собираюсь go рискнуть и предположить, что вы открыты для редактирования кода пакета, чтобы исправить любые ошибки, с которыми вы сталкиваетесь из-за проблем совместимости, поскольку большинство пакетов, которые не обновлялись довольно долгое время, могут столкнуться с их.

Чтобы решить проблему ограничения количества файлов, которые может загрузить пользователь, пакет 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 на pypi

1 голос
/ 19 июля 2020

Я полагаю, что вы можете использовать загрузку нескольких файлов в Django, еще не поступило в сообщество. Выдержка:

Если вы хотите загрузить несколько файлов с помощью одного поля формы, установите атрибут 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
            ...

Адресация комментариев: с точки зрения эстетики c , вы можете многое сделать со стилем ...

Однако загрузка asyn c (отдельные запросы POST) значительно усложняет проверку и взаимодействие с пользователем:

  • Первый файл может завершиться sh после 2-го, так что вы собираетесь отрицать, если количество> 3.
  • Проверка внешнего интерфейса может быть взломана, поэтому вы не можете на нее полагаться, но бэкэнд-валидация разделена на несколько запросов, которые с точки зрения пользователя представляют собой одно действие.
  • Но если файлы поступают не по порядку, некоторые из них преуспевают, а некоторые нет, как вы собираетесь предоставить обратную связь пользователю?
  • Если приходят 1, 3 и 4, но пользователя больше волнуют 1, 2, 3 - пользователь должен предпринять несколько действий, чтобы исправить ситуацию.

Один почтовый запрос:

  • вышедших из строя заездов нет
  • Вы можете использовать подход «все терпит неудачу или все успешно», который прозрачен для конечного пользователя и легко корректируется.
  • Вероятно, что порядок файловых массивов является предпочтительным для пользователя, поэтому даже если вы допустите частичный успех, вы, вероятно, поступите правильно.
0 голосов
/ 14 июля 2020

Суть в том, что вы используете jQuery .each для загрузки изображений через AJAX. Каждый запрос POST к вашему представлению Django представляет собой загрузку одного файла, но может быть несколько запросов одновременно.

Попробуйте следующее:

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 ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...