Django Uygulamalarında Excel, CSV, JSON gibi Farklı Formattaki Veriyi İçe ve Dışa Aktarma

Django ile web uygulamaları geliştirdiğimizde veri tabanına farklı formatlarda verilerimizi aktarmak veya veritabanındaki verileri tablo halinde elde etmek isteriz. django-import-export paketi bizim için bu işlemi kolaylaştıran bir pakettir. Paket Excel, CSV, JSON gibi farklı formattaki verileri desteklemektedir.

Kurulum

Paket kurulumu için pip paket yöneticisini kullanarak paketi bilgisayarımıza yüklüyoruz.

pip install django-import-export

Daha sonra settings.py dosyamıza aşağıdaki kodları ekleyerek uygulamamıza tanımlıyoruz. Ayrıca veriyi içe(import) veya dışa(export) aktarma yaparken bir problem çıkarsa veri bütünlüğünü sağlamak için IMPORT_EXPORT_USE_TRANSACTIONS = True olarak tanımlıyoruz.

INSTALLED_APPS = (
    ...
    'import_export',
)
# The default value is False
IMPORT_EXPORT_USE_TRANSACTIONS = True

django-import-export paketini kullanırken paketin nasıl çalışacağını tanımladığımız model sınıflarımıza benzeyen Resource konsepti bulunmaktadır. Bunun için uygulamamızın içinde resource.py isminde dosya oluşturuyoruz.

models.py dosyamızda Category ve Comment adında iki model tanımladık. Comment’ leri django-import-export paketi ile içe ve dışa aktaracağız.

models.py
from django.db import models

class Category(models.Model):
    title = models.CharField(max_length = 500, unique=True) 

    def __str__(self):
        return "{0}".format(self.title)

class Comment(models.Model):
    description = models.TextField()
    category = models.ForeignKey(Category, on_delete= models.CASCADE )

resources.py dosyasında hangi model kullanılacağını tanımlıyoruz.

resources.py

from import_export import resources
from label.models import Comment

class CommentResource(resources.ModelResource):
    class Meta:
        model = Comment

Dosya yapısı aşağıdaki resimdeki gibidir:

Veriyi Dışa Aktarma

Veritabanımızdaki Comment tablosunu frontendde bir form oluşturarak kullanıcının istediği formatta bilgisayarına indirebilmesini sağlayacağız. Bunu için ilk önce urls.py path’ imizi tanımlayacağız.

path('export-data/', export_data, name="export_data"),

Daha sonra export_data.html isminde template i oluşturuyoruz. Bu template ‘ te formumuzu oluşturalım.

{% block content %}
<div class="card card-secondary">
  <div class="card-header">
    <h3 class="card-title">Export Comments</h3>
  </div>
  <div class="card-body">
    <form role="form" method="POST" action="{% url 'label:export_data' %}" enctype="multipart/form-data">
      {% csrf_token %}
        <div class="form-group">
          <label>Choose Format Type</label>
          <select class="custom-select" name="file-format">
            <option selected>Choose format...</option>
            <option>CSV</option>
            <option>JSON</option>
            <option>XLS (Excel)</option>
          </select>
        </div> <br><br><br>
      <button type="submit" class="btn btn-info btn-block">Export</button>
    </form>
  </div>
</div>
{% endblock %} 

views.py

views.py dosyasında form post edildiğinde çalışacak fonksiyonu tanımladık.

from django.http import HttpResponse
from .resources import CommentResource
def export_data(request):
    if request.method == 'POST':
        # Get selected option from form
        file_format = request.POST['file-format']
        comment_resource = CommentResource()
        dataset = comment_resource.export()
        if file_format == 'CSV':
            response = HttpResponse(dataset.csv, content_type='text/csv')
            response['Content-Disposition'] = 'attachment; filename="exported_data.csv"'
            return response        
        elif file_format == 'JSON':
            response = HttpResponse(dataset.json, content_type='application/json')
            response['Content-Disposition'] = 'attachment; filename="exported_data.json"'
            return response
        elif file_format == 'XLS (Excel)':
            response = HttpResponse(dataset.xls, content_type='application/vnd.ms-excel')
            response['Content-Disposition'] = 'attachment; filename="exported_data.xls"'
            return response   
    return render(request, 'label/export_import_data_page.html') 

Veriyi İçe Aktarma

Frontend’ de form oluşturarak kullanıcının CSV veya JSON formatındaki verisini veritabanına yükleyeceğiz. İlk önce form post edildiğinde çalışacak path’ i tanımlıyoruz.

path('import-data/', import_data, name="import_data"),

Aşağıda templatimizde kullanılacak formun kodu bulunmaktadır.

{% block content %}
<div class="card card-secondary">
  <div class="card-header">
    <h3 class="card-title">Import Comments</h3>
  </div>
  <div class="card-body">
    <form role="form" method="POST" action="{% url 'label:import_data' %}" enctype="multipart/form-data">
      {% csrf_token %}
      <div class="form-group">
        <label>Choose Format Type</label><br>           
        <input class="mb-2" type="file" name="importData">
        <select class="custom-select" name="file-format1"> 
          <option selected>Choose format...</option>
          <option>CSV</option>
          <option>JSON</option>
        </select>
      </div> <br><br><br>
      <button type="submit" class="btn btn-info btn-block">Import</button>
    </form>
  </div>
</div>
{% endblock %} 

views.py

from tablib import Dataset
def import_data(request):
    if request.method == 'POST':
        file_format = request.POST['file-format1']
        comment_resource = CommentResource()
        dataset = Dataset()
        new_comments = request.FILES['importData']

        if file_format == 'CSV':
            imported_data = dataset.load(new_comments.read().decode('utf-8'),format='csv')
            result = comment_resource.import_data(dataset, dry_run=True)                                                                 
        elif file_format == 'JSON':
            imported_data = dataset.load(new_comments.read().decode('utf-8'),format='json')
            # Testing data import
            result = comment_resource.import_data(dataset, dry_run=True) 

        if not result.has_errors():
            # Import now
            comment_resource.import_data(dataset, dry_run=False)

    return render(request, 'label/export_import_data_page.html')')

Category modelini Comment modelimize bire çok ilişkisi bulunmaktadır. Bu ilişkiyide yönetebilmemiz için resource.py dosyamızı değiştimemiz gerekiyor.

resources.py

from import_export import resources, widgets, fields
from label.models import Comment, Category

class CharRequiredWidget(widgets.CharWidget):
    def clean(self, value, row=None, *args, **kwargs):
        val = super().clean(value)
        if val:
            return val
        else:
            raise ValueError('this field is required')

class ForeignkeyRequiredWidget(widgets.ForeignKeyWidget):
    def clean(self, value, row=None, *args, **kwargs):
        if value:
            print(self.field, value)
            return self.get_queryset(value, row, *args, **kwargs).get(**{self.field: value})
        else:
            raise ValueError(self.field+ " required")

class CommentResource(resources.ModelResource):
    category = fields.Field(column_name='category', attribute='category', widget=ForeignkeyRequiredWidget(Category, 'title'),
                        saves_null_values=False) # title Category modelindeki kolon ismi
    description = fields.Field(saves_null_values=False, column_name='description', attribute='description',
                          widget=CharRequiredWidget())

    class Meta:
        model = Comment
        fields = ('id', 'description', 'category')
        clean_model_instances = True

# class CommentResource(resources.ModelResource):
#     class Meta:
#         model = Comment

Aşağıdaki CSV dosya yapısı ile uygulamamızı deneyebiliriz.

id,category,description
1,Computer,Lorem ipsum dolor sit amet, consectetur 
2,Computer,adipiscing elit, sed do eiusmod tempor incididunt ut 
3,TV,labore et dolore magna aliqua. Ut enim ad minim veniam, quis nost  
4,TV,Sed ut perspiciatis unde omnis iste natus error sit voluptatem 
5,TV,accusantium doloremque laudantium, totam rem aperiam, eaque ipsa

Django Admin Paneli

admin.py dosyasına aşağıdaki kodları ekleyerek Django Admin panelinde comment modelimiz için içe ve dışa aktarma işlemlerini kolayca yapabiliriz.

from django.contrib import admin
from label.models import  Comment
from import_export.admin import ImportExportModelAdmin

@admin.register(Comment)
class CommentAdmin(ImportExportModelAdmin):
    pass

Yukarıdaki kodları eklediğimizde Admin panelinde Comment modeline girdiğimizde sağ üstte IMPORT ve EXPORT adında iki düğmenin belirdiğini göreceksiniz. EXPORT düğmesine basarak aşağıdaki resimde de görüldüğü gibi bir çok formatta Comment tablomuzu dışa aktarabiliriz.

Export dediğimizde çıkacak ekranda hangi formatta olmasını tanımlayacağımız form karşımıza çıkacaktır.

Eğer Admin panelinde Export ve Import formlarını customize etmek istiyorsak admin.py deki dosyamızı aşağıdaki şekilde düzenleyebiliriz.

from django.contrib import admin
from label.models import  Comment, Category
from import_export.admin import ImportExportModelAdmin
# 1 METHOD
# @admin.register(Comment)
# class CommentAdmin(ImportExportModelAdmin):
#     pass

# 2 METHOD
from .resources import CommentResource
class CommentAdmin(ImportExportModelAdmin):
    resource_class = CommentResource
    list_display = ('description', 'category')

admin.site.register(Comment, CommentAdmin)

Django projelerinde kullanabileceğiniz çok kullanışlı bir pakettir. Bu yazıda kullanılabilecek tüm durumları basit ve açık bir şekilde anlatmaya çalıştım. Başarılar.

Kaynaklar
  1. https://django-import-export.readthedocs.io/en/latest/index.html