繁体   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 数据库

编辑考虑下面的评论,谢谢!

我正在编写一个用于存储产品信息的小型 Django 应用程序。 我使用Custom Management Command编写了用于上传本地csv文件的后端逻辑,并将其连接到前端。

我在实现文件上传时遇到问题 -> 让用户上传products.csv通过Form提交来用文件填充数据库并在一页上显示所有产品。

我已将我之前的示例以及下面建议的代码精简为最简单的格式以尝试定位问题。

csv文件示例:

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

模型.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'

用于单个product CRUD 操作和 CSV 文件上传的表格。

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'}))

/模板/上传.html

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

视图.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')
          

form.cleaned_data['csv_file']更改为request.FILES['sent_file']可以正确打印文件名uploads.csvurl仍然无法访问并且仍然在form.save()上崩溃。 我可以将上传文件的内容打印到终端的唯一方法是添加以下内容:

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

输出:

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

但是文件仍然无法上传, form.save()也无法执行。

我不确定如何继续调试它。 如果有人能指出我正确的方向,将不胜感激!

为了保存 CSV 文件,您将创建一个 function 以读取 csv 文件并保存产品详细信息:但您也可以重构代码以满足您的需求。

  • 首先使用 Product() 上传并保存文件
  • 获取文件路径并读取内容如果model字段和csv列的名称相同会更好
  • 遍历每一行并创建一个字典,其中仅包含迭代中的产品详细信息
  • 创建 Product() 的实例并将字典传递给它并保存
  • 对于外键,使用 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()

您的代码应如下所示:

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')

感谢这篇SO 帖子,我能够通过使用生成器逐行解码 CSV 来找到答案。

这是代码: 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