Дикий Григорий

Full-stack веб-разработчик

Интернет-магазин на Django. Экспорт заказов в CSV, расширение админ-панели. Часть 9

Доброго времени суток! Иногда вам может понадобится экспортировать данные в формате CSV, чтобы потом использовать их в других ПО. CSV— текстовый формат, предназначенный для представления табличных данных. Для работы с этим форматом есть пакет csv который можно импортировать в наш Django проект.

Мы будем кастомизировать нашу админку и добавлять опции для работы с CSV. Мы можем написать сами опцию в админ-панели если будем получать в качестве параметров:

  • Текущую ModelAdmin
  • Текущий request объект
  • И QuerySet для объектов выбраных пользователем

Давайте начнем с того, что изменим файл admin.py в директории orders и напишем функцию для экспорта:

from django.http import HttpResponse
import csv
import datetime


def ExportToCSV(modeladmin, request, queryset):
    opts = modeladmin.model._meta
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; \
        filename=Orders-{}.csv'.format(datetime.datetime.now().strftime("%d/%m/%Y"))
    writer = csv.writer(response)

    fields = [field for field in opts.get_fields() if not field.many_to_many and not field.one_to_many]
    # Первая строка- оглавления
    writer.writerow([field.verbose_name for field in fields])
    # Заполняем информацией
    for obj in queryset:
        data_row = []
        for field in fields:
            value = getattr(obj, field.name)
            if isinstance(value, datetime.datetime):
                value = value.strftime('%d/%m/%Y')
            data_row.append(value)
        writer.writerow(data_row)
    return response
    ExportToCSV.short_description = 'Export CSV'

Разберем код подробнее:

  1. Мы создаем экземпляр объекта HttpResponse c content_type='text/csv', чтобы браузер мог различать этот файл. Также мы заполнили Content-Disposition, чтобы сообщить браузеру, что ответ будет содержать в себе файл.
  2. Мы создали CSV объект writer, чтобы туда записывать данный. В нашем случае мы будем записывать их в response.
  3. При помощи метода get_fields мы получаем поля модели исключая из них те, у которых отношения: многие-ко-многим и один-к-одному.
  4. Первым делом записываем шапку
  5. Далее итерируя по QuerySet, rкоторый возвращает нам объект мы записываем данные. А также мы проверяем поля на дату, так как CSV понимает только string формат.

Добавляем эту функцию в нашу админ-панель, для этого допишем в класс OrderAdmin следующее:

class OrderAdmin(admin.ModelAdmin):
    ...
    actions = [ExportToCSV]

Зайдя в админку мы должны увидеть эту опцию:

Расширение админ-панели

Иногда бывает нужно изменить админ-панель, добавить собственное отображение элементов. Для этого вы должны написать собственное отображение в админ-панели. Изменим файл views.py из директории orders следующим образом:

from django.shortcuts import render, get_object_or_404
from .models import OrderItem, Order
...
from django.contrib.admin.views.decorators import staff_member_required


@staff_member_required
def AdminOrderDetail(request, order_id):
    order = get_object_or_404(Order, id=order_id)
    return render(request, 'admin/orders/order/detail.html', {'order': order})

staff_member_required - декоратор, который проверяет, если пользователь зарегистрирован и имеет доступ к интерфейсу администратора. Сама же функция AdminOrderDetail() принимает идентификатор заказа и рендерит страницу с этим заказом.

Теперь нам надо настроить маршрутизатор для этого отображения. Для этого откройте файл urls.py из директории orders и вставьте следующий код:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^create/$', views.OrderCreate, name='OrderCreate'),
    url(r'^admin/order/(?P<order_id>\d+)/$', views.AdminOrderDetail, name='AdminOrderDetail')
]

Теперь нам надо создать следующую структуру файлов в директории templates в приложении orders:

admin/
    orders/
        order/
            detail.html

Создадим файл detail.html и заполним его следующим содержимым:

{% extends "admin/base_site.html" %}

{% load static %}

{% block title %}
  Заказ {{ order.id }} {{ block.super }}
{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
  <a href="{% url "admin:index" %}">Главная</a> ›
  <a href="{% url "admin:orders_order_changelist" %}">Заказы</a> ›
  <a href="{% url "admin:orders_order_change" order.id %}">Заказ {{ order.id }}</a> ›
  Детали
</div>
{% endblock %}

{% block content %}
  <h1>Заказ {{ order.id }}</h1>
  <ul class="object-tools">
    <li>
      <a href="#" onclick="window.print()">Распечатать заказ</a>
    </li>
  </ul>
  <table width="100%">
    <tr>
      <th>Создан</th>
      <td>{{ order.created }}</td>
    </tr>
    <tr>
      <th>Заказчик</th>
      <td>{{ order.first_name }} {{ order.last_name }}</td>
    </tr>
    <tr>
      <th>E-mail</th>
      <td><a href="mailto:{{ order.email }}">{{ order.email }}</a></td>
    </tr>
    <tr>
      <th>Адрес</th>
      <td>{{ order.address }}</td>
    </tr>
    <tr>
      <th>Полная стоймость</th>
      <td>{{ order.get_total_cost }} руб.</td>
    </tr>
    <tr>
      <th>Статус</th>
      <td>{% if order.paid %}Оплачен{% else %}В ожидании оплаты{% endif %}</td>
    </tr>
  </table>

  <div class="module">
    <div class="tabular inline-related list-related">
      <table>
        <h2>Заказанные товары</h2>
        <thead>
          <tr>
            <th>Товар</th>
            <th>Цена</th>
            <th>Количество</th>
            <th>Стоймость всего</th>
          </tr>
        </thead>
        <tbody>
          {% for item in order.items.all  %}
            <tr class="row{% cycle "1" "2" %}">
              <td>{{ item.product.name }}</td>
              <td>{{ item.price }} руб.</td>
              <td>{{ item.quantity }}</td>
              <td>{{ item.get_cost }} руб.</td>
            </tr>
          {% endfor %}
          <tr class="total">
            <td colspan="3">Всего</td>
            <td class="num">{{ order.get_total_cost }} руб.</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
{% endblock %}

Это шаблон для отображения товара в админ-панели. Этот шаблон наследуется от admin/base_site.html, который содержит стили и HTML код. Также мы добавляем собственные стили css/admin.css. Теперь нам нужно добавить ссылку на наш шаблон в админ-панели. Для этого нужно отредактировать файл admin.py в директории orders добавив следующий код:

from django.core.urlresolvers import reverse
from django.utils.html import format_html

def OrderDetail(obj):
    return format_html('<a href="{}">Посмотреть</a>'.format(
        reverse('orders:AdminOrderDetail', args=[obj.id])
    ))

Эта функция получает объект заказа и возвращает ссылку на страницу полного заказа. format_html - позволяет использовать HTML теги, так как Django по умолчанию их убирает.

Изменим класс OrderAdmin следующим образом:

class OrderAdmin(admin.ModelAdmin):
    list_display = [..., OrderDetail]

Теперь войдя в админ-панель на страницу заказов и выбрав ссылку посмотреть, в итоге вы должны увидеть следующее:

Репозиторий проекта: github