Add admin import action and lot of other work
This commit is contained in:
parent
704c9a6a66
commit
a0ae56e4e3
13
README.rst
13
README.rst
|
@ -1,3 +1,16 @@
|
|||
====================
|
||||
django-import-export
|
||||
====================
|
||||
|
||||
Example app
|
||||
-----------
|
||||
|
||||
::
|
||||
cd tests && ./manage.py runserver
|
||||
|
||||
Username and password for admin are 'admin', 'password'.
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
* exporting
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import tempfile
|
||||
import tablib
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from django.template.response import TemplateResponse
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from .forms import ImportForm
|
||||
from .core import Importer
|
||||
|
||||
|
||||
class ImportMixin(object):
|
||||
|
||||
change_list_template = 'admin/import_export/change_list_import.html'
|
||||
import_template_name = 'admin/import_export/import.html'
|
||||
importer_class = Importer
|
||||
format_choices = (
|
||||
('', '---'),
|
||||
('csv', 'CSV'),
|
||||
('xls', 'Excel XLS'),
|
||||
)
|
||||
|
||||
def get_urls(self):
|
||||
urls = super(ImportMixin, self).get_urls()
|
||||
info = self.model._meta.app_label, self.model._meta.module_name
|
||||
my_urls = patterns('',
|
||||
url(r'^import/$',
|
||||
self.admin_site.admin_view(self.import_action),
|
||||
name='%s_%s_import' % info),
|
||||
)
|
||||
return my_urls + urls
|
||||
|
||||
def get_importer_class(self):
|
||||
return self.importer_class
|
||||
|
||||
def get_format(self, format_name):
|
||||
return getattr(tablib.formats, format_name)
|
||||
|
||||
def import_action(self, request, *args, **kwargs):
|
||||
importer_class = self.get_importer_class()
|
||||
if request.POST and request.POST.get('tmp_file'):
|
||||
import_file = open(request.POST.get('tmp_file'))
|
||||
result = self.importer_class(import_file,
|
||||
model=self.model,
|
||||
format=self.get_format(request.POST.get('input_format')),
|
||||
dry_run=False,
|
||||
raise_errors=True).run()
|
||||
success_message = _('Import finished')
|
||||
messages.success(request, success_message)
|
||||
import_file.close()
|
||||
return HttpResponseRedirect('..')
|
||||
|
||||
form = ImportForm(self.format_choices,
|
||||
request.POST or None,
|
||||
request.FILES or None)
|
||||
result = None
|
||||
tmp_file_name = None
|
||||
input_format = None
|
||||
if request.POST:
|
||||
if form.is_valid():
|
||||
import_file = request.FILES['import_file']
|
||||
input_format = form.cleaned_data['input_format']
|
||||
result = importer_class(import_file,
|
||||
model=self.model,
|
||||
raise_errors=False,
|
||||
format=self.get_format(input_format),
|
||||
dry_run=True).run()
|
||||
if not result.has_errors():
|
||||
tmp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
for chunk in import_file.chunks():
|
||||
tmp_file.write(chunk)
|
||||
tmp_file.close()
|
||||
tmp_file_name = tmp_file.name
|
||||
fields = importer_class(model=self.model).get_mapping().keys()
|
||||
context = {
|
||||
'form': form,
|
||||
'result': result,
|
||||
'opts': self.model._meta,
|
||||
'fields': fields,
|
||||
'tmp_file': tmp_file_name,
|
||||
'input_format': input_format,
|
||||
}
|
||||
return TemplateResponse(request, [self.import_template_name],
|
||||
context, current_app=self.admin_site.name)
|
|
@ -12,6 +12,9 @@ class RowResult(object):
|
|||
self.orig_fields = []
|
||||
self.fields = []
|
||||
|
||||
def combined_fields(self):
|
||||
return zip(self.orig_fields, self.fields)
|
||||
|
||||
|
||||
class Result(list):
|
||||
|
||||
|
@ -20,7 +23,8 @@ class Result(list):
|
|||
self.base_errors = []
|
||||
|
||||
def row_errors(self):
|
||||
return [(i, row.errors) for i, row in enumerate(self) if row.errors]
|
||||
return [(i + 1, row.errors)
|
||||
for i, row in enumerate(self) if row.errors]
|
||||
|
||||
def has_errors(self):
|
||||
return self.base_errors or self.row_errors()
|
||||
|
@ -30,15 +34,19 @@ class Importer(object):
|
|||
model = None
|
||||
format = None
|
||||
import_code = "ID"
|
||||
raise_errors = True
|
||||
raise_errors = False
|
||||
dry_run = True
|
||||
mapping = None
|
||||
from_encoding = None
|
||||
|
||||
def __init__(self, f, **kwargs):
|
||||
def __init__(self, f=None, **kwargs):
|
||||
self.f = f
|
||||
for key, value in kwargs.iteritems():
|
||||
setattr(self, key, value)
|
||||
|
||||
def set_stream(self, f):
|
||||
self.f = f
|
||||
|
||||
def get_mapping(self):
|
||||
if self.mapping:
|
||||
return self.mapping
|
||||
|
@ -46,7 +54,10 @@ class Importer(object):
|
|||
return OrderedDict(mapping)
|
||||
|
||||
def load_dataset(self):
|
||||
text = unicode(self.f.read(), 'cp1250').encode('utf-8')
|
||||
if self.from_encoding:
|
||||
text = unicode(self.f.read(), self.from_encoding).encode('utf-8')
|
||||
else:
|
||||
text = self.f.read()
|
||||
if not self.format:
|
||||
self.data = tablib.import_set(text)
|
||||
else:
|
||||
|
@ -85,7 +96,7 @@ class Importer(object):
|
|||
self.load_dataset()
|
||||
except Exception, e:
|
||||
result.base_errors.append(_('Loading error') +
|
||||
u': %s' % unicode(e))
|
||||
u': %s' % repr(e))
|
||||
if self.raise_errors:
|
||||
raise
|
||||
return result
|
||||
|
@ -100,7 +111,7 @@ class Importer(object):
|
|||
self.save_instance(instance)
|
||||
row_result.fields = self.get_representation(instance)
|
||||
except Exception, e:
|
||||
row_result.errors.append(unicode(e))
|
||||
row_result.errors.append(repr(e))
|
||||
if self.raise_errors:
|
||||
raise
|
||||
result.append(row_result)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class ImportForm(forms.Form):
|
||||
import_file = forms.FileField(
|
||||
label=_('File to import')
|
||||
)
|
||||
input_format = forms.ChoiceField(
|
||||
label=_('Format'),
|
||||
choices=(),
|
||||
)
|
||||
|
||||
def __init__(self, format_choices, *args, **kwargs):
|
||||
super(ImportForm, self).__init__(*args, **kwargs)
|
||||
self.fields['input_format'].choices = format_choices
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n admin_static admin_modify %}
|
||||
{% load admin_urls %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
|
||||
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
|
||||
{% if not is_popup %}
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_label|capfirst|escape }}</a>
|
||||
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
||||
› {% block breadcrumbs_last %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
<li><a href="import/" class="import_link">{% trans "Import" %}</a></li>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
{% extends "admin/import_export/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load import_export_tags %}
|
||||
|
||||
{% block breadcrumbs_last %}
|
||||
{% trans "Import" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Import" %}</h1>
|
||||
|
||||
{% if not result or result.has_errors %}
|
||||
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<p>
|
||||
{% trans "This importer excpect and will import following fields: " %}
|
||||
{{ fields|join:", " }}.
|
||||
</p>
|
||||
|
||||
<fieldset class="module aligned">
|
||||
{% for field in form %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
|
||||
{{ field.label_tag }}
|
||||
|
||||
{{ field }}
|
||||
|
||||
{% if field.field.help_text %}
|
||||
<p class="help">{{ field.field.help_text|safe }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
|
||||
<div class="submit-row">
|
||||
<input type="submit" class="default" value="{% trans "Submit" %}">
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if result %}
|
||||
{% if result.has_errors %}
|
||||
<h2>{% trans "Errors" %}</h2>
|
||||
<ul>
|
||||
{% for error in result.base_errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
{% for line, error in result.row_errors %}
|
||||
<li>{% trans "Line number" %}: {{ line }} - {{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<form action="" method="POST">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned">
|
||||
<input type="hidden" name="tmp_file" value="{{ tmp_file }}">
|
||||
<input type="hidden" name="input_format" value="{{ input_format }}">
|
||||
<p>
|
||||
{% trans "Bellow is a preview of applied import data. If you are satisfied with a result click Confirm import" %}
|
||||
</p>
|
||||
<div class="submit-row">
|
||||
<input type="submit" class="default" name="confirm" value="{% trans "Confirm import" %}">
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<h2>
|
||||
{% trans "Preview" %}
|
||||
</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for field in fields %}
|
||||
<th>{{ field }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% for row in result %}
|
||||
<tr>
|
||||
{% for f in row.combined_fields %}
|
||||
<td>
|
||||
{% compare_values f.0 f.1 %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
|||
from diff_match_patch import diff_match_patch
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def compare_values(value1, value2):
|
||||
dmp = diff_match_patch()
|
||||
diff = dmp.diff_main(value1, value2)
|
||||
dmp.diff_cleanupSemantic(diff)
|
||||
html = dmp.diff_prettyHtml(diff)
|
||||
return html
|
|
@ -1,2 +1,3 @@
|
|||
Django>=1.4
|
||||
tablib
|
||||
diff-match-patch
|
||||
|
|
1
setup.py
1
setup.py
|
@ -15,6 +15,7 @@ CLASSIFIERS = [
|
|||
install_requires = [
|
||||
'tablib',
|
||||
'Django>=1.4.2',
|
||||
'diff-match-patch',
|
||||
]
|
||||
|
||||
setup(
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from import_export.admin import ImportMixin
|
||||
|
||||
from .models import Book
|
||||
|
||||
|
||||
class BookAdmin(ImportMixin, admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(Book, BookAdmin)
|
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.path.pardir))
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
|
@ -1,9 +1,4 @@
|
|||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'haystack_tests.db',
|
||||
}
|
||||
}
|
||||
import os.path
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
|
@ -18,3 +13,16 @@ INSTALLED_APPS = [
|
|||
]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
ROOT_URLCONF = "urls"
|
||||
|
||||
DEBUG = True
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(os.path.dirname(__file__), 'database.db'),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from django.conf.urls.defaults import patterns, include
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
|
||||
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
Loading…
Reference in New Issue