csv 上传的自定义 Django 管理页面出错(对象没有属性 'model')
Error in custom Django admin page on csv uploads (object has no attribute 'model')
我一直在尝试在我的 Django 管理页面中创建一个自定义上传按钮,但我一直收到指向我的 CsvUploader.py 文件的错误:object has no attribute 'model'
我有一个非常简单的模型:
class Link(models.Model):
name = models.CharField(db_column='Name', max_length=50)
url = models.URLField(db_column="URL", max_length=500)
def __str__(self):
return f"{self.name}: {self.url}"
我也修改了我的管理模板,如下:
#Extend Admin portal use
class CsvUploadAdmin(admin.ModelAdmin):
change_list_template = "custom_admin/csv_form.html"
def get_urls(self):
urls = super().get_urls()
additional_urls = [
path("upload-csv/", self.upload_csv),
]
return additional_urls + urls
urls = property(get_urls)
def changelist_view(self, request, extra_context=None):
extra = extra_context or {}
extra["csv_upload_form"] = CsvUploadForm()
return super(CsvUploadAdmin, self).changelist_view(request, extra_context=extra)
def upload_csv(self, request):
if request.method == "POST":
form = CsvUploadForm(request.POST, request.FILES)
if form.is_valid():
if request.FILES['csv_file'].name.endswith('csv'):
try:
decoded_file = request.FILES['csv_file'].read().decode('utf-8')
except UnicodeDecodeError as e:
self.message_user(
request,
"There was an error decoding the file:{}".format(e),
level=messages.ERROR
)
return redirect("..")
# Here we will call our class method
io_string = io.StringIO(decoded_file)
uploader = CsvUploader(io_string, self.model, portal)
result = uploader.create_records()
else:
self.message_user(
request,
"Incorrect file type: {}".format(
request.FILES['csv_file'].name.split(".")[1]
),
level=messages.ERROR
)
else:
self.message_user(
request,
"There was an error in the form {}".format(form.errors),
level=messages.ERROR
)
return redirect("..")
@admin.register(Link)
class LinkAdmin(CsvUploadAdmin):
pass
最后,这是我的 CsvUploader.py 文件:
import csv
from portal.models import Link
from django.db.utils import IntegrityError
from django.core.exceptions import FieldDoesNotExist
from django.db import transaction
from django.conf.urls import *
class CsvUploader:
def __init__(self, csv_file, model_name, app_name):
self.reader = list(csv.DictReader(csv_file, delimiter=','))
self.keys = [k for k in self.reader[0]]
self.model_fields = [f.name for f in self.model._meta.get_fields()]
self.valid = self._validate_csv()
self.csv_pos = 0
def _validate_csv(self):
keys = []
for k in self.keys:
if k.endswith("_id"):
keys.append(k[:-3])
else:
keys.append(k)
return set(keys).issubset(self.model_fields)
def read_chunk(self):
chunk = []
for i in range(1000):
try:
chunk.append(self.model(**self.reader[self.csv_pos]))
except IndexError as e:
print(e)
break
self.csv_pos += 1
return chunk
def create_records(self):
if not self.valid:
return "Invalid csv file"
while True:
chunk = self.read_chunk()
if not chunk:
break
try:
with transaction.atomic():
self.model.objects.bulk_create(chunk)
except IntegrityError as e:
for i in chunk:
try:
i.save()
except IntegrityError:
continue
print("Exception: {}".format(e))
return "records successfully saved!"
我希望得到第二组眼睛来发现上面第 13 行的错误:
'CsvUploader'对象没有属性'model'
请求方法:POST
请求 URL:http://127.0.0.1:8000/admin/portal/link/upload-csv/
姜戈版本:3.2.4
异常类型:属性错误
异常值:
'CsvUploader' 对象没有属性 'model'
非常感谢任何帮助!
问题是您期望 self.model 成为 Link
模型对象,因为您在 CsvUploadAdmin
中调用如下:-
uploader = CsvUploader(io_string, self.model, portal)
这是不正确的,您在 __init__
方法中定义的 model_name 变量中有 self.model
值,例如:-
def __init__(self, csv_file, model_name, app_name):
因此,为了解决您的问题,您需要在 __init__
方法
中添加以下行
self.model = model_name
所以最终的方法是
def __init__(self, csv_file, model_name, app_name):
self.reader = list(csv.DictReader(csv_file, delimiter=','))
self.keys = [k for k in self.reader[0]]
self.model = model_name
self.model_fields = [f.name for f in self.model._meta.get_fields()]
self.valid = self._validate_csv()
self.csv_pos = 0
我认为有一种更简单的方法可以在管理页面上创建上传/导入功能,如果我理解正确的话post。
在您的管理模板中,您可以执行与以下类似的操作:
from django.contrib import messages, admin
from django.contrib.contenttypes.models import ContentType
from django.http import *
from django.shortcuts import redirect, render, HttpResponseRedirect
from django.urls import include, path, reverse
from portals.models import Links
from django.conf.urls import *
from django import forms
from portals.actions import export_as_csv_action
#Extend Admin portal use
class CsvImportForm(forms.Form):
csv_upload = forms.FileField()
@admin.register(Links)
class LinksAdmin(admin.ModelAdmin):
actions = [export_as_csv_action("Export Selected as CSV", fields=['name', 'url'], index=False)]
def get_urls(self):
urls = super().get_urls()
new_urls = [path('upload-csv/', self.upload_csv),]
return new_urls + urls
def upload_csv(self, request):
if request.method == "POST":
csv_file = request.FILES["csv_upload"]
if not csv_file.name.endswith('.csv'):
messages.warning(request, 'The wrong file type was uploaded.')
return HttpResponseRedirect(request.path_info)
file_data = csv_file.read().decode("utf-8")
csv_data = file_data.split("\n")
for x in csv_data:
flds = x.split(",")
if x:
Links.objects.update_or_create(
name = flds[0],
url = flds[1],
)
return redirect("..")
form = CsvImportForm()
data = {"form": form}
return render(request, "admin/csv_upload.html", data)
**注意:上面没有验证,例如确保存在相同数量的列等。您也想这样做...
然后在模板文件夹中创建一个名为 admin 的文件夹,在其中创建另一个名为 portals 的文件夹和一个名为 csv_upload.html 的文件
csv_upload.html 文件应该是这样的:
{
"%" "extends",
"admin/base.html" "%"
}
{% block content %}
<div>
<form action="." method="POST" enctype="multipart/form-data">
{{ form.as_p }}
{% csrf_token %}
<button type='submit'>Upload File</button>
</form>
</div>
{% endblock%}
然后在您的 portals 文件夹中,创建一个 links 文件夹,并在 links 文件夹中创建一个名为 change_list.html 的文件,如下所示:
{
"%" "extends",
"admin/change_list.html" "%"
}
{% load static %}
{% block content %}
<b><a href='upload-csv/'>Upload a CSV file.</a></b>
{{block.super}}
{% endblock %}
我已经测试过了,它在我这边起作用了。
我一直在尝试在我的 Django 管理页面中创建一个自定义上传按钮,但我一直收到指向我的 CsvUploader.py 文件的错误:object has no attribute 'model'
我有一个非常简单的模型:
class Link(models.Model):
name = models.CharField(db_column='Name', max_length=50)
url = models.URLField(db_column="URL", max_length=500)
def __str__(self):
return f"{self.name}: {self.url}"
我也修改了我的管理模板,如下:
#Extend Admin portal use
class CsvUploadAdmin(admin.ModelAdmin):
change_list_template = "custom_admin/csv_form.html"
def get_urls(self):
urls = super().get_urls()
additional_urls = [
path("upload-csv/", self.upload_csv),
]
return additional_urls + urls
urls = property(get_urls)
def changelist_view(self, request, extra_context=None):
extra = extra_context or {}
extra["csv_upload_form"] = CsvUploadForm()
return super(CsvUploadAdmin, self).changelist_view(request, extra_context=extra)
def upload_csv(self, request):
if request.method == "POST":
form = CsvUploadForm(request.POST, request.FILES)
if form.is_valid():
if request.FILES['csv_file'].name.endswith('csv'):
try:
decoded_file = request.FILES['csv_file'].read().decode('utf-8')
except UnicodeDecodeError as e:
self.message_user(
request,
"There was an error decoding the file:{}".format(e),
level=messages.ERROR
)
return redirect("..")
# Here we will call our class method
io_string = io.StringIO(decoded_file)
uploader = CsvUploader(io_string, self.model, portal)
result = uploader.create_records()
else:
self.message_user(
request,
"Incorrect file type: {}".format(
request.FILES['csv_file'].name.split(".")[1]
),
level=messages.ERROR
)
else:
self.message_user(
request,
"There was an error in the form {}".format(form.errors),
level=messages.ERROR
)
return redirect("..")
@admin.register(Link)
class LinkAdmin(CsvUploadAdmin):
pass
最后,这是我的 CsvUploader.py 文件:
import csv
from portal.models import Link
from django.db.utils import IntegrityError
from django.core.exceptions import FieldDoesNotExist
from django.db import transaction
from django.conf.urls import *
class CsvUploader:
def __init__(self, csv_file, model_name, app_name):
self.reader = list(csv.DictReader(csv_file, delimiter=','))
self.keys = [k for k in self.reader[0]]
self.model_fields = [f.name for f in self.model._meta.get_fields()]
self.valid = self._validate_csv()
self.csv_pos = 0
def _validate_csv(self):
keys = []
for k in self.keys:
if k.endswith("_id"):
keys.append(k[:-3])
else:
keys.append(k)
return set(keys).issubset(self.model_fields)
def read_chunk(self):
chunk = []
for i in range(1000):
try:
chunk.append(self.model(**self.reader[self.csv_pos]))
except IndexError as e:
print(e)
break
self.csv_pos += 1
return chunk
def create_records(self):
if not self.valid:
return "Invalid csv file"
while True:
chunk = self.read_chunk()
if not chunk:
break
try:
with transaction.atomic():
self.model.objects.bulk_create(chunk)
except IntegrityError as e:
for i in chunk:
try:
i.save()
except IntegrityError:
continue
print("Exception: {}".format(e))
return "records successfully saved!"
我希望得到第二组眼睛来发现上面第 13 行的错误:
'CsvUploader'对象没有属性'model'
请求方法:POST
请求 URL:http://127.0.0.1:8000/admin/portal/link/upload-csv/
姜戈版本:3.2.4
异常类型:属性错误
异常值:
'CsvUploader' 对象没有属性 'model'
非常感谢任何帮助!
问题是您期望 self.model 成为 Link
模型对象,因为您在 CsvUploadAdmin
中调用如下:-
uploader = CsvUploader(io_string, self.model, portal)
这是不正确的,您在 __init__
方法中定义的 model_name 变量中有 self.model
值,例如:-
def __init__(self, csv_file, model_name, app_name):
因此,为了解决您的问题,您需要在 __init__
方法
self.model = model_name
所以最终的方法是
def __init__(self, csv_file, model_name, app_name):
self.reader = list(csv.DictReader(csv_file, delimiter=','))
self.keys = [k for k in self.reader[0]]
self.model = model_name
self.model_fields = [f.name for f in self.model._meta.get_fields()]
self.valid = self._validate_csv()
self.csv_pos = 0
我认为有一种更简单的方法可以在管理页面上创建上传/导入功能,如果我理解正确的话post。
在您的管理模板中,您可以执行与以下类似的操作:
from django.contrib import messages, admin
from django.contrib.contenttypes.models import ContentType
from django.http import *
from django.shortcuts import redirect, render, HttpResponseRedirect
from django.urls import include, path, reverse
from portals.models import Links
from django.conf.urls import *
from django import forms
from portals.actions import export_as_csv_action
#Extend Admin portal use
class CsvImportForm(forms.Form):
csv_upload = forms.FileField()
@admin.register(Links)
class LinksAdmin(admin.ModelAdmin):
actions = [export_as_csv_action("Export Selected as CSV", fields=['name', 'url'], index=False)]
def get_urls(self):
urls = super().get_urls()
new_urls = [path('upload-csv/', self.upload_csv),]
return new_urls + urls
def upload_csv(self, request):
if request.method == "POST":
csv_file = request.FILES["csv_upload"]
if not csv_file.name.endswith('.csv'):
messages.warning(request, 'The wrong file type was uploaded.')
return HttpResponseRedirect(request.path_info)
file_data = csv_file.read().decode("utf-8")
csv_data = file_data.split("\n")
for x in csv_data:
flds = x.split(",")
if x:
Links.objects.update_or_create(
name = flds[0],
url = flds[1],
)
return redirect("..")
form = CsvImportForm()
data = {"form": form}
return render(request, "admin/csv_upload.html", data)
**注意:上面没有验证,例如确保存在相同数量的列等。您也想这样做...
然后在模板文件夹中创建一个名为 admin 的文件夹,在其中创建另一个名为 portals 的文件夹和一个名为 csv_upload.html 的文件 csv_upload.html 文件应该是这样的:
{
"%" "extends",
"admin/base.html" "%"
}
{% block content %}
<div>
<form action="." method="POST" enctype="multipart/form-data">
{{ form.as_p }}
{% csrf_token %}
<button type='submit'>Upload File</button>
</form>
</div>
{% endblock%}
然后在您的 portals 文件夹中,创建一个 links 文件夹,并在 links 文件夹中创建一个名为 change_list.html 的文件,如下所示:
{
"%" "extends",
"admin/change_list.html" "%"
}
{% load static %}
{% block content %}
<b><a href='upload-csv/'>Upload a CSV file.</a></b>
{{block.super}}
{% endblock %}
我已经测试过了,它在我这边起作用了。