Django import-export - letting users import data

谁说胖子不能爱 提交于 2020-08-09 15:03:41

问题


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 references
  • attribute defines the attribute on the Word 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!