问题
I am trying to use django import-export to let users import their own data. I've integrated it with the admin, and that works well, but I'm having trouble getting the user import side to work.
Here are my views:
from .models import WordResource
from tablib import Dataset
from .models import Word
from django.contrib import messages
# Word import
def import_words(request):
if request.method == 'POST':
file_format = request.POST['file-format']
word_resource = WordResource()
dataset = Dataset()
new_words = request.FILES['importData']
if file_format == 'CSV':
imported_data = dataset.load(new_words.read().decode('utf-8'),format='csv')
result = word_resource.import_data(dataset, dry_run=True, raise_errors=True)
elif file_format == 'XLSX':
imported_data = dataset.load(new_words.read(),format='xlsx')
result = word_resource.import_data(dataset, dry_run=True, raise_errors=True)
if result.has_errors():
messages.error(request, 'Uh oh! Something went wrong...')
else:
# Import now
word_resource.import_data(dataset, dry_run=False)
messages.success(request, 'Your words were successfully imported')
return render(request, 'vocab/import.html')
My WordResource:
from import_export import resources
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
class WordResource(resources.ModelResource):
target_word = Field(attribute='target_word', column_name='Russian')
source_word = Field(attribute='source_word', column_name='Meaning')
example_sentence = Field(attribute='example_sentence', column_name='Example sentence')
fluency = Field(attribute='fluency', column_name='Fluency level')
deck_name = Field(attribute='deck_name', column_name='Deck name')
username = Field(attribute='username', column_name='username',widget=ForeignKeyWidget(User, 'username'))
class Meta:
model = Word
fields = ("username", "target_word",'source_word','example_sentence',
'fluency', 'deck_name',)
import_order = fields
skip_unchanged = True
# exclude = ('id',)
import_id_fields = ['username']
My Word model:
class Word(models.Model):
target_word = models.CharField('Word in Russian',max_length=25,help_text="The word you want to add, in Russian")
source_word = models.CharField('What does it mean?',max_length=25, help_text="Write down the translation in any language")
add_to_word_list = models.BooleanField('Would you like to create a flashcard?', default=True)
deck_name = models.CharField('Deck name', max_length=25)
example_sentence = models.CharField(max_length=150,blank=True,help_text="It's important to learn words in context!")
## how well you know the word
class Fluency(models.IntegerChoices):
Beginner = 0
Lower_intermediate = 1
Upper_intermediate = 2
Advanced = 3
fluency = models.IntegerField(choices=Fluency.choices, help_text="How well do you know this word?",null=False)
user = models.ForeignKey(User, related_name="words",on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.target_word
def get_absolute_url(self):
return reverse("vocab:detail",
kwargs={"username": self.user.username, "pk": self.pk})
class Meta:
ordering = ["target_word"]
constraints = [
models.UniqueConstraint(fields=['user','target_word', 'source_word'],name='unique_word')]
And my import.html template:
{% extends "vocab/vocab_base.html" %}
{% load bootstrap %}
{% block content %}
{% if messages %}
<div class="messages">
{% for message in messages %}
<h3 {% if message.tags %} class=" {{ message.tags }} " {% endif %}> {{ message }} </h3>
{% endfor %}
</div>
{% else %}
<h1> Import your words</h1>
<p>Here you can import your words from a csv or excel file.</p>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="importData">
<p>Please select the format of your file</p>
<select name="file-format" class="form-control my-3">
<option selected>Choose format...</option>
<option>CSV</option>
<option>XLSX</option>
</select>
<button class="btn btn-primary" type="submit">Import</button>
</form>
<a href="{% url 'vocab:index' %}">Back</a>
{% endif %}
{% endblock %}
Strangely, it seemed to work initially when I tried with a csv file, but not with an xlsx file. I then made changes just to the xlsx part of my code. Not only did it not fix the problem, now csv doesn't work either.
Note - it's not due to the id column, as I do have a blank id column in my test upload files.
Update - I realised that I was missing the user field, which is a foreign (not null) key on my Word model. So I added a ForeignKeyWidget, but I'm getting the following error: NOT NULL constraint failed: vocab_word.user_id
. The error persists even if I add a column with the user id. How can I fix this?
My csv file looks like this:
username;Russian;Meaning;Example Sentence;Fluency level;Deck name
testuser;word1;word2;one two three;1;new
Traceback after implementing Matthew's changes:
Environment:
Request Method: POST
Request URL: http://127.0.0.1:8000/vocab/import/
Django Version: 3.0.3
Python Version: 3.8.2
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'rest_framework',
'bootstrap4',
'bootstrapform',
'languages',
'django_countries',
'import_export',
'django_tables2',
'django_filters',
'accounts',
'vocab',
'flash',
'api',
'django_cleanup.apps.CleanupConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback (most recent call last):
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\sqlite3\base.py", line 396, in execute
return Database.Cursor.execute(self, query, params)
The above exception (NOT NULL constraint failed: vocab_word.user_id) was the direct cause of the following exception:
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\mvren\OneDrive\Documents\Coding\Russki\mysite\vocab\views.py", line 115, in import_words
result = word_resource.import_data(dataset, dry_run=True, raise_errors=True)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\import_export\resources.py", line 627, in import_data
return self.import_data_inner(dataset, dry_run, raise_errors, using_transactions, collect_failed_rows, **kwargs)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\import_export\resources.py", line 673, in import_data_inner
raise row_result.errors[-1].error
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\import_export\resources.py", line 569, in import_row
self.save_instance(instance, using_transactions, dry_run)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\import_export\resources.py", line 352, in save_instance
instance.save()
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\models\base.py", line 745, in save
self.save_base(using=using, force_insert=force_insert,
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\models\base.py", line 782, in save_base
updated = self._save_table(
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\models\base.py", line 887, in _save_table
results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\models\base.py", line 924, in _do_insert
return manager._insert(
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\models\query.py", line 1204, in _insert
return query.get_compiler(using=using).execute_sql(returning_fields)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\models\sql\compiler.py", line 1384, in execute_sql
cursor.execute(sql, params)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\utils.py", line 100, in execute
return super().execute(sql, params)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\utils.py", line 68, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\utils.py", line 77, in _execute_with_wrappers
return executor(sql, params, many, context)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\utils.py", line 86, in _execute
return self.cursor.execute(sql, params)
File "C:\Users\mvren\miniconda3\envs\myRuEnv\lib\site-packages\django\db\backends\sqlite3\base.py", line 396, in execute
return Database.Cursor.execute(self, query, params)
Exception Type: IntegrityError at /vocab/import/
Exception Value: NOT NULL constraint failed: vocab_word.user_id
回答1:
Your error is quite clear - the object cannot be created because the user_id
field is null at the time of creation:
NOT NULL constraint failed: vocab_word.user_id
Your csv contains:
username;Russian;Meaning;Example Sentence;Fluency level;Deck name
testuser;word1;word2;one two three;1;new
Your Word
model also defines a User field:
user = models.ForeignKey(User, related_name="words",on_delete=models.CASCADE)
This means that when you declare a Resource
in django-import-export, you need to specify how the csv username
can be mapped to any existing user
instances via the FK relationship.
You should use ForeignKeyWidget
for this, because that handles mapping csv fields to objects.
column_name
defines the csv column we use to lookup user referencesattribute
defines the attribute on theWord
model that is to be set
Also, we need to ensure that User relations are looked for using the "correct" field. From the docs:
The lookup field defaults to using the primary key (pk) as lookup criterion but can be customised to use any field on the related model.
Putting it all together, our Field
definition looks like this:
userid = fields.Field(column_name='username', attribute='user', widget=widgets.ForeignKeyWidget(User, "username")
So I think the source of your error was that you were incorrectly setting attribute
to username
. If you debugged the code, you would probably find that the 'testuser' User
instance had been loaded, and assigned to Word.username
, which will just be ignored, and Word.user
will be null, hence the error.
Update
Another issue to correct:
The fields
declaration should reference the model attributes which are to be set from csv data.
Therefore the username
field should user
, because this is the model attribute to be updated.
fields = ("user", "target_word",'source_word','example_sentence', 'fluency', 'deck_name',)
来源:https://stackoverflow.com/questions/62443320/django-import-export-letting-users-import-data