Django Rest Framework экспорт данных CSV из моделей в фоновом режиме - PullRequest
1 голос
/ 14 января 2020

В настоящее время я использую DRF версии 3.9.2. Я делал экспорт CSV из моделей синхронно. Пример кодовых блоков, описанных ниже:

urls.py

from django.urls import path

urlpatterns = [
    path('api/users-csv-export/', UsersExportAsCSV.as_view())
]

views.py

from rest_framework.views import APIView

def get_users_data():
    queryset = User.objects.only('first_name', 'last_name', 'created_at', 'email', 'gender', 'date_of_birth') 
    fields = ['first_name', 'last_name', 'created_at', 'email', 'gender', 'date_of_birth']
    titles = ['First Name', 'Last Name', 'Date Added', 'Email', 'Gender', 'Date of Birth']
    file_name = 'users'
    return queryset, fields, titles, file_name

class UsersExportAsCSV(APIView):
    def get(self, request):
        users = get_users_data()
        data = export_to_csv(queryset=users[0], fields=users[1], titles=users[2], file_name=users[3])
        return data

utils.py

def export_to_csv(queryset, fields, titles, file_name):
    """
    will export the model data in the form of csv file
    :param queryset: queryset that need to be exported as csv
    :param fields: fields of a model that will be included in csv
    :param titles: title for each cell of the csv record
    :param file_name: the exported csv file name
    :return:
    """
    model = queryset.model
    response = HttpResponse(content_type='text/csv')
    # force download
    response['Content-Disposition'] = 'attachment; filename={}.csv'.format(file_name)
    # the csv writer
    writer = csv.writer(response)
    if fields:
        headers = fields
        if titles:
            titles = titles
        else:
            titles = headers
    else:
        headers = []
        for field in model._meta.fields:
            headers.append(field.name)
        titles = headers

    # Writes the title for the file
    writer.writerow(titles)

    # write data rows
    for item in queryset:
        writer.writerow([nested_getattr(item, field) for field in headers])
    return response

По мере роста данных цикл запрос-ответ становится тяжелее. Чтобы этот запрос не блокировал другие запросы Threading vs Asyn c task какой из них предпочтительнее? Есть ли хорошая идея по оптимизации цикла запрос-ответ.

Здесь пользователь должен дождаться окончания экспорта и завершения загрузки. В данном случае желаемый результат заключается в том, что всякий раз, когда пользователь посещает URL-адрес, мгновенный ответ «генерируемый файл» и создание и загрузка файла в фоновом режиме.

Любая помощь по этой теме c будет высоко ценится. .

Ответы [ 3 ]

2 голосов
/ 14 января 2020

Я думаю, что вы можете достичь с этим http://www.celeryproject.org/

Вы можете использовать "shared_task", чтобы завершить задачу для создания файла CSV в фоновом режиме и сохранить его в таблицу (например: Скачайте FileModel), если он уже завершен sh. Затем вы можете загрузить его позже.
Ответьте на текущее представление, перенаправив его в DetailView (запись DownloadFileModel с нулевым значением в поле файла), который вы подготовили для загрузки файла, если файл не готов, просто дайте описание, чтобы подождать, пока файл готов (и вы можете назначить файл записи).

1 голос
/ 14 января 2020

Я думаю, вы можете использовать сельдерей для запуска асинхронной задачи c, это даст большую функциональность, отобразит прогресс и когда задача будет завершена.

https://pypi.org/project/django-celery/

from celery import task

def get_users_data():
    queryset = list(User.objects.values_list('first_name', 'last_name', 'created_at', 'email', 'gender', 'date_of_birth')) 
    fields = ['first_name', 'last_name', 'created_at', 'email', 'gender', 'date_of_birth']
    titles = ['First Name', 'Last Name', 'Date Added', 'Email', 'Gender', 'Date of Birth']
    file_name = 'users'
    return queryset, fields, titles, file_name



@task
def export_to_csv(queryset, fields, titles, file_name):
    """
    will export the model data in the form of csv file
    :param queryset: queryset that need to be exported as csv
    :param fields: fields of a model that will be included in csv
    :param titles: title for each cell of the csv record
    :param file_name: the exported csv file name
    :return:
    """
    model = queryset.model
    response = HttpResponse(content_type='text/csv')
    # force download
    response['Content-Disposition'] = 'attachment; filename={}.csv'.format(file_name)
    # the csv writer
    writer = csv.writer(response)
    if fields:
        headers = fields
        if titles:
            titles = titles
        else:
            titles = headers
    else:
        headers = []
        for field in model._meta.fields:
            headers.append(field.name)
        titles = headers

    # Writes the title for the file
    writer.writerow(titles)

    # write data rows
    # here you can save the file at particular path 
    for item in queryset:
        writer.writerow([nested_getattr(item, field) for field in headers])
    return file_path


class UsersExportAsCSV(APIView):
    def get(self, request):
        users = get_users_data()
        task_id = export_to_csv.delay(queryset=users[0], fields=users[1], titles=users[2], file_name=users[3])
        return task_id

using the task id you can get the result of that

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

0 голосов
/ 15 января 2020

Чтобы решить мою проблему, я использовал модуль потоков python и для хранения файловой записи я использовал сервер кэширования Redis.

Сначала метод GET проверяет, доступна ли экспортированная запись файла в кеше или нет. Если он доступен, то URL для этого файла отправляется в ответ. Метод POST сгенерирует файл и запишет его в media / temp и сохранит запись в кеше.

В кодовые блоки вносятся некоторые изменения, которые объясняются следующим образом:

views.py

from rest_framework.views import APIView

def get_users_data():
    queryset = User.objects.only('first_name', 'last_name', 'created_at', 'email', 'gender', 'date_of_birth') 
    fields = ['first_name', 'last_name', 'created_at', 'email', 'gender', 'date_of_birth']
    titles = ['First Name', 'Last Name', 'Date Added', 'Email', 'Gender', 'Date of Birth']
    return queryset, fields, titles, file_name

class TripHistoryExportAsCSV(APIView):
    file_name = "users_all"
    file_extension = 'csv'

    def post(self, request):
        try:
            queryset = get_users_data()[0]
            fields = get_users_data()[1]
            titles = get_users_data()[2]
            x = threading.Thread(target=export_to_csv, args=(queryset, fields, titles, self.file_name))
            x.start()
            return Response({
                'message': 'CSV file is generating'
            })
        except EmptyResultSet:
            return Response({
                'message': 'Can not create CSV file'
            }, status=status.HTTP_200_OK)

    def get(self, request):
        data = check_export_data_in_cache(self.file_name, self.file_extension)
        if data:
            return Response({
                'url': data.get('report_url')
            })
        else:
            return Response({
                'message': 'Generation of new files required'
            }, status=status.HTTP_204_NO_CONTENT)

utils.py

def nested_getattr(obj, attribute, split_rule='__'):
    """
    This function is responsible for getting the nested record from the given obj parameter
    :param obj: whole item without splitting
    :param attribute: field after splitting
    :param split_rule:
    :return:
    """
    split_attr = attribute.split(split_rule)
    for attr in split_attr:
        if not obj:
            break
        obj = getattr(obj, attr)
    return obj


def export_to_csv(queryset, fields, titles, file_name):
    """
    will export the model data in the form of csv file
    :param queryset: queryset that need to be exported as csv
    :param fields: fields of a model that will be included in csv
    :param titles: title for each cell of the csv record
    :param file_name: the exported csv file name
    :return:
    """
    model = queryset.model
    import os
    from yatruadminbackend.settings import MEDIA_ROOT
    if fields:
        headers = fields
        if titles:
            titles = titles
        else:
            titles = headers
    else:
        headers = []
        for field in model._meta.fields:
            headers.append(field.name)
        titles = headers

    with open(os.path.join(MEDIA_ROOT, f'temp/{file_name}.csv'), 'w', newline='') as file:
        # Writes the title for the file
        writer = csv.writer(file)
        writer.writerow(titles)
        # write data rows
        for item in queryset:
            writer.writerow([nested_getattr(item, field) for field in headers])
        set_cache_for_export_file(file_name, 'csv')


def set_cache_for_export_file(filename, extension):
    generated_date = timezone.now()
    export_file_name = f'{filename}_{extension}'
    record_in_cache = {
        'key': export_file_name,
        'report_url': f'{BACKEND_URL}media/temp/{filename}.csv',
        'generated_on': generated_date
    }
    cache.set(export_file_name, record_in_cache, 300)
    print(cache.get(export_file_name))


def check_export_data_in_cache(file_name, file_extension):
    cache_key = f'{file_name}_{file_extension}'
    print(cache_key)
    if cache.get(cache_key):
        return cache.get(cache_key)
...