简体   繁体   English

Django 如何使用表单上传 CSV 文件以填充 postgres 数据库并在浏览器中显示所有项目

[英]Django how to upload CSV file using Form to populate postgres database and display all items in browser

Django 3.2.1, Python 3.6, Postgres database Django 3.2.1,Python 3.6,Postgres 数据库

EDITED to take into account comments below, thanks!编辑考虑下面的评论,谢谢!

I am writing a small Django app for storing product information.我正在编写一个用于存储产品信息的小型 Django 应用程序。 I coded the backend logic for uploading a local csv file using a Custom Management Command and am connecting this to the front end.我使用Custom Management Command编写了用于上传本地csv文件的后端逻辑,并将其连接到前端。

I am having trouble implementing the file upload -> having user upload products.csv via a Form submission to populate the database with file and display all products on one page.我在实现文件上传时遇到问题 -> 让用户上传products.csv通过Form提交来用文件填充数据库并在一页上显示所有产品。

I have stripped down my previous examples, as well as the suggested code below, to the simplest format to try to locate the problem.我已将我之前的示例以及下面建议的代码精简为最简单的格式以尝试定位问题。

Example of the csv file: csv文件示例:

name,sku,description
Brian James,skus-look-like-this,The products will have various descriptions. And multiple lines too.

models.py模型.py

class Product(models.Model):
    name = models.CharField(max_length=500)
    sku = models.CharField(max_length=500)
    description = models.TextField(blank=False, null=False)
    status = models.TextField(blank=False, null=False, default='inactive')

    class Meta:
        db_table = 'product'

Form for individual product CRUD operations and for CSV file upload.用于单个product CRUD 操作和 CSV 文件上传的表格。

forms.py forms.py

class UploadForm(forms.Form):
    csv_file = forms.FileField(required=False, widget=forms.FileInput(attrs={'class': 'form-control', 'placeholder':
        'Upload "products.csv"', 'help_text': 'Choose a .csv file with products to enter'}))

/templates/upload.html /模板/上传.html

<form method="post" enctype="multipart/form-data">
        {% csrf_token %}
    <input type="file" name="sent_file" />
    <input type="submit" name="submit" value="Upload" />
</form>

views.py视图.py

# Function to upload the form, parse it, save to database
def create_upload(request):
    if request.method == 'GET':
        form = UploadForm()
        return render(request, 'upload.html', {'form': form})

    # If not GET method then proceed
    form = UploadForm(request.POST, request.FILES)
    print('FIRST FORM', form)

    # Validate the form
    if form.is_valid():
            csv_file = form.cleaned_data['csv_file']
            # Errors begin here ^, print(csv_file) = 'None'

            form.save()
            # Crashes here ^ with error: "AttributeError: 'UploadForm' object has no attribute 'save'
"
            file_path = os.path.join(BASE_DIR, form.csv_file.url)
            # printing `file_path` = `AttributeError: 'InMemoryUploadedFile' object has no attribute 'url'
`
        
            # read the file contents and save the product details
            with open(f'{file_path}, r') as products_csv:

            products_file = csv.reader(products_csv)
            next(products_file)  # skip header row

            for counter, line in enumerate(products_file):

                name = line[0]
                sku = line[1]
                description = line[2]

                p = Product()
                p.name = name
                p.sku = sku
                p.description = description
                p.status = random.choice(['active', 'inactive'])
                p.save()

    return redirect('/show_product')
          

Changing form.cleaned_data['csv_file'] to request.FILES['sent_file'] correctly prints the file name uploads.csv but the url is still inaccessible and still crashes on form.save() .form.cleaned_data['csv_file']更改为request.FILES['sent_file']可以正确打印文件名uploads.csvurl仍然无法访问并且仍然在form.save()上崩溃。 The only way I can print to terminal the contents of the uploaded file is by adding this:我可以将上传文件的内容打印到终端的唯一方法是添加以下内容:

csv_file = request.FILES['sent_file']
for i in csv_file:
    print(i)

outputs:输出:

b"'name','sku','description'\n"
b"'Zed','some-skus-more','descriptions. galore.'\n"

But the file still can't be uploaded and form.save() can't be implemented.但是文件仍然无法上传, form.save()也无法执行。

I'm not sure how to continue debugging this.我不确定如何继续调试它。 If anyone can point me in the right direction, would really appreciate it!如果有人能指出我正确的方向,将不胜感激!

In other to save CSV files you will create a function to read the csv file and save product details: but you can as well refactor the code to meet your suit.为了保存 CSV 文件,您将创建一个 function 以读取 csv 文件并保存产品详细信息:但您也可以重构代码以满足您的需求。

  • Upload and save the file first using Product()首先使用 Product() 上传并保存文件
  • Get the path for the file and read the contents It would be better if you have the same names for model fields and csv columns获取文件路径并读取内容如果model字段和csv列的名称相同会更好
  • Loop through each line and create a dictionary which contains only a product details at an iteration遍历每一行并创建一个字典,其中仅包含迭代中的产品详细信息
  • Make an instance of Product() and pass the dictionary to it and save创建 Product() 的实例并将字典传递给它并保存
  • For the foreign key, get the object from Product() using get() accordingly with the value that is stored in csv对于外键,使用 get() 从 Product() 中获取 object,相应地使用存储在 csv 中的值
# You could save the Product details in two ways

new_product = Product()
new_product.registration_number = fields[0]
new_product.name = fields[1]
# like so for other fields

new_product.save()
.....
# Create a model object, create a dictionary of key values where keys corresponds to the field names of the model.

# create a dictionary `new_product_details` containing values of a product

new_product = Product()
new_product.__dict__.update(new_product_details)
new_product.save()

import csv
def save_new_product_from_csv(file_path):
    # do try catch accordingly
    # open csv file, read lines
    with open(file_path, 'r') as fp:
        products = csv.reader(fp, delimiter=',')
        row = 0
        for product in products:
            if row==0:
                headers = product
                row = row + 1
            else:
                # create a dictionary of product details
                new_product_details = {}
                for i in range(len(headers)):
                    new_product_details[headers[i]] = product[i]

                # for the foreign key field you should get the object first and reassign the value to the key
                new_product_details['product'] = Product.objects.get() # get the record according to value which is stored in db and csv file

                # create an instance of product model
                new_product = Product()
                new_product.__dict__.update(new_product_details)
                new_product.save()
                row = row + 1
        fp.close()

Your code should look something like this after:您的代码应如下所示:

def uploadcsv(request):
    if request.method == 'GET':
        form = UploadForm()
        return render(request, 'upload.html', {'form':form})

    # If not GET method then proceed
    try:
        form = UploadForm(data=request.POST, files=request.FILES)
        if form.is_valid():
            csv_file = form.cleaned_data['csv_file']
            if not csv_file.name.endswith('.csv'):
                messages.error(request, 'File is not CSV type')
                return redirect('/show_product')
            # If file is too large
            if csv_file.multiple_chunks():
                messages.error(request, 'Uploaded file is too big (%.2f MB)' %(csv_file.size(1000*1000),))
                return redirect('/show_product')

            # save and upload file 
            form.save()

            # get the path of the file saved in the server
            file_path = os.path.join(BASE_DIR, form.csv_file.url)

            # a function to read the file contents and save the product details
            save_new_product_from_csv(file_path)
            # do try catch if necessary
                
    except Exception as e:
        logging.getLogger('error_logger').error('Unable to upload file. ' + repr(e))
        messages.error(request, 'Unable to upload file. ' + repr(e))
    return redirect('/show_product')

Thanks to this SO post I was able to find an answer by using a generator to decode the CSV line by line.感谢这篇SO 帖子,我能够通过使用生成器逐行解码 CSV 来找到答案。

Here is the code: views.py这是代码: views.py

def decode_utf8(line_iterator):
    for line in line_iterator:
        yield line.decode('utf-8')


    
def create_upload(request):
    if request.method == 'GET':
        form = UploadForm()
        return render(request, 'upload.html', {'form': form})

    form = UploadForm(request.POST, request.FILES)

    # Validate the form
    if form.is_valid():

        # Get the correct type string instead of byte without reading full file into memory with a generator to decode line by line
        products_file = csv.reader(decode_utf8(request.FILES['sent_file']))
        next(products_file)  # Skip header row

        for counter, line in enumerate(products_file):
            name = line[0]
            sku = line[1]
            description = line[2]

            p = Product()
            p.name = name
            p.sku = sku
            p.description = description
            p.save()

        messages.success(request, 'Saved successfully!')

        return redirect('/show_product')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM