如何使用 TemplateHTMLRenderer 在 Django-REST-Framework 中创建/放置?

Posted

技术标签:

【中文标题】如何使用 TemplateHTMLRenderer 在 Django-REST-Framework 中创建/放置?【英文标题】:How do I use TemplateHTMLRenderer for Create/Put in Django-REST-Framework? 【发布时间】:2019-04-15 12:45:20 【问题描述】:

我很难找到正确的方法来初始化 TemplateHTMLRenderer 以呈现用于创建对象的表单。我的 API 非常适合发布(DRF 可浏览 API 表单),但这不是前端解决方案。

我可以使用文档和模板包创建详细信息页面:https://www.django-rest-framework.org/topics/html-and-forms/#rendering-forms。文档非常清楚如何创建 UPDATE 表单。

但我不能在我的一生中初始化相同的表单来进行初始创建。

当有自动生成表单的功能时,我不想手动编写表单来与创建对象的 API 交互......但我完全不知所措。

来自 views.py 的工作更新视图:

class LicensedSoftwareDetail(APIView):
    model = LicensedSoftware
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'licsoftware-detail.html'

    def get(self, request, pk):
        if pk:
            licensedsoftware = get_object_or_404(LicensedSoftware, pk=pk)
            serializer = LicensedSoftwareSerializer(licensedsoftware)
            return Response('serializer': serializer, 'licensedsoftware': licensedsoftware)
        else:
            serializer = LicensedSoftwareSerializer()
            return Response('serializer': serializer)

    def post(self, request, pk):
        if pk:
            licensedsoftware = get_object_or_404(LicensedSoftware, pk=pk)
            serializer = LicensedSoftwareSerializer(licensedsoftware, data=request.data)
            if not serializer.is_valid():
                return Response('serializer': serializer, 'licensedsoftware': licensedsoftware)
            serializer.save()
        else:
            serializer = LicensedSoftwareSerializer(data=request.data)
            serializer.save()

        return redirect('../../licsoftware/')

它是一个带有自定义更新和创建方法的嵌套 (OneToOne) 序列化程序。

来自 serializers.py:

class SoftwareSerializer(WritableNestedModelSerializer):

    class Meta:
        model = Software
        fields = '__all__'

class LicensedSoftwareSerializer(WritableNestedModelSerializer):

    software = SoftwareSerializer()
    available = serializers.SerializerMethodField()

    class Meta:
        model = LicensedSoftware
        fields = '__all__'
        read_only_fields = ('available',)

    def get_available(self, obj):
        return int(obj.numpurchased) - obj.software.assignees.count()

    def update(self, instance, validated_data):
        software_data = validated_data.pop('software')
        software = instance.software
        for attr, value in software_data.items():
            if attr == 'assignees':
                instance.software.assignees.set(value)
            else:
                setattr(software, attr, value)
        software.save()
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

    def create(self, instance, validated_data):
        software_data = validated_data.pop('software')
        software = instance.software
        for attr, value in software_data.items():
            if attr == 'assignees':
                instance.software.assignees.set(value)
            else:
                setattr(software, attr, value)
        software.save()
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

我有一个引用 TemplateHTMLRenderer 表单的模板,当它可以调用 PK 时,它非常适合更新。但是,我不能创建一个视图或 URL 来拉取表单以进行创建/发布。

型号:

class Software (models.Model):
    brand = models.CharField(max_length=50, blank=True, null=False)
    title = models.CharField(max_length=50, blank=True, null=False, verbose_name="Software Title")
    version = models.CharField(max_length=50, blank=True, null=False)
    website = models.CharField(max_length=100, blank=True, null=False)
    active = models.BooleanField()
    notes = models.CharField(max_length=1000, blank=True, null=True)
    assignees = models.ManyToManyField(User, related_name='software_assigned', blank=True, verbose_name="Installed Users:")

    def __str__(self):
        return "0 1 Version 2".format(self.brand, self.title, str(self.version))


class LicensedSoftware(models.Model):

    software = models.OneToOneField(Software, primary_key=True, on_delete=models.CASCADE)
    vehicle = models.CharField(max_length=50, blank=True, null=False, verbose_name="Contract/Purchase Vehicle")
    vendor = models.CharField(max_length=50, blank=True, null=False)
    licensekey = models.CharField(max_length=50, blank=True, null=False)
    subscription = models.BooleanField()
    term = models.CharField(max_length=50, blank=True, null=False)
    renewaldate = models.DateField(blank=True, null=True, verbose_name="Renew By")
    supportincluded = models.BooleanField(verbose_name="Support Included")
    numpurchased = models.IntegerField(blank=True, null=False, verbose_name="Licenses Purchased")

不工作且不包含 PK 的附加视图:

class LicensedSoftwareList(APIView):
    queryset = LicensedSoftware.objects.all()
    serializer_class = LicensedSoftwareSerializer
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'licsoftware-detail.html'

    def get(self, request, format=None):
        licsoftware = LicensedSoftware.objects.all()
        serializer = LicensedSoftwareSerializer(licsoftware)
        return Response('result': serializer.data)

    def post(self, request, format=None):
        serializer = LicensedSoftwareSerializer(data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

模板:

% extends 'base.html' %
% load rest_framework %
% block content %
<h1>Licensed Software -  licensedsoftware.software.brand   licensedsoftware.software.title   licensedsoftware.software.version </h1>

<form class="form-horizontal" method="post" novalidate>
    % csrf_token %
    % render_form serializer template_pack='rest_framework/horizontal'%
   <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">Submit!</button>
        </div>
    </div>
</form>

% endblock %

网址:

urlpatterns = [
    path('', views.index, name='index'),
    path('inventory/', views.inventory, name='inventory'),
    path('schema/', schema_view),
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
    path('inventory/mobile-detail/<int:pk>/', views.MobileDetail.as_view()),
    path('inventory/licsoftware-detail/<int:pk>/', views.LicensedSoftwareDetail.as_view()),
    path('inventory/licsoftware-detail/', views.LicensedSoftwareList.as_view()),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    path('docs/', include_docs_urls(title='API Documentation')),
    path('profiles/', views.ProfileList.as_view()),
    path('profile-detail/<int:pk>/', views.ProfileDetail.as_view()),
    path('inventory/licsoftware/', licensedsoftware_view, name='licensed_table'),
    path('inventory/mobile/', mobile_view, name='mobile_table'),

API 网址有效。可浏览的 API 将允许创建/发布。 路径('inventory/licsoftware-detail//',views.LicensedSoftwareDetail.as_view()),作品 path('inventory/licsoftware-detail/', views.LicensedSoftwareList.as_view()),给出错误:

在序列化程序 LicensedSoftwareSerializer 上尝试获取字段 software 的值时出现 AttributeError。 序列化程序字段可能命名不正确,并且与 QuerySet 实例上的任何属性或键都不匹配。 原始异常文本是:“QuerySet”对象没有“软件”属性。

【问题讨论】:

为什么要在响应中返回序列化程序?你得到什么具体的错误?你到底想完成什么? 你的 urls.py 我们的 API 路由是什么样子的? 我只想要一个自动生成的输入表单来在上面的嵌套序列化模型中创建新项目 需要更多信息。显示:模型、模板和特定错误,否则我们无法进行。 已编辑以添加请求的其余信息。 【参考方案1】:

好的,我理解您希望 DRF 自动生成用于创建和更新许可软件的表单的方式。因此,我尝试这样做,但方式与您的方式略有不同,即我使用 ModelViewSet 代替 APIView,并且我更改了两个序列化程序的创建函数。

序列化器.py

class SoftwareSerializer(serializers.ModelSerializer):
    class Meta:
        model = Software
        fields = '__all__'

    def create(self, validated_data):
        assignees = validated_data.pop('assignees')

        software = Software.objects.create(**validated_data)
        for user in assignees:
            user = User.objects.get(id=user.id)
            software.assignees.add(user)
        return software


class LicensedSoftwareSerializer(serializers.ModelSerializer):
    software = SoftwareSerializer()
    available = serializers.SerializerMethodField()

    class Meta:
        model = LicensedSoftware
        fields = '__all__'
        read_only_fields = ('available',)

    def get_available(self, obj):
        return int(obj.numpurchased) - obj.software.assignees.count()

    def update(self, instance, validated_data):
        software_data = validated_data.pop('software')
        software = instance.software
        for attr, value in software_data.items():
            if attr == 'assignees':
                instance.software.assignees.set(value)
            else:
                setattr(software, attr, value)
        software.save()
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        return instance

    def create(self, validated_data):
        software_data = validated_data.pop('software')
        assignee = software_data.pop("assignees")
        user_list = []
        for user in assignee:
            user_list.append(user.id)

        software_data.update('assignees': user_list)

        software_serializer = SoftwareSerializer(data=software_data)

        software_serializer.is_valid()
        software = software_serializer.save()

        validated_data.update("software": software)

        ls = LicensedSoftware.objects.create(**validated_data)

        return ls

views.py

class LicensedSoftwareDetail(ModelViewSet):

    queryset = LicensedSoftware.objects.all()
    serializer_class = LicensedSoftwareSerializer

如果您想在此处进行任何更改,请随时提及。并且作为一个建议,我会说创建一个单独的端点来创建软件,然后为 LicensedSoftwareSerializer 使用 PrimaryKeyRelated 字段或任何其他此类字段,因为当您使用嵌套序列化程序时事情会变得丑陋。 和平

编辑:

好的,你没有pk的观点对于生成创建模板有点错误。 这就是我让它工作的方式:

class LicensedSoftwareCreate(APIView):
    queryset = LicensedSoftware.objects.all()
    serializer_class = LicensedSoftwareSerializer
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'licsoftware-detail.html'

    def get(self, request, format=None):

        serializer = LicensedSoftwareSerializer()
        return Response('serializer': serializer)

    def post(self, request, format=None):
        serializer = LicensedSoftwareSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

希望对你有帮助。

【讨论】:

我遇到的问题是这些函数在我当前的代码中运行良好,在 API 视图中。我需要一个使用 TemplateHTMLRenderer 的前端表单,当我可以调用 PK 时,它可以正常工作,但无法找到生成空白表单的视图代码。 如果我误解了,请原谅我,但在这里,“当自动生成表单的功能存在时,我不想手动编写表单来与创建对象的 API 交互......但是我完全不知所措。”你不是要求 DRF 自动创建表单以进行更新和创建 是的。确实如此。它会自动创建使用此代码更新的表单:django-rest-framework.org/topics/html-and-forms/… 好的。我所写的内容为创建和更新以及列出数据库中的所有对象生成表单。这不正是你想要的。 它们出现在 API 中。我的问题是将它们呈现在前端 HTML 模板中,以便用户进行创建。我找不到关于在我的模板中显示该表单的文档。因此,如果您的代码这样做,我只需要在模板中调用该表单的标签。

以上是关于如何使用 TemplateHTMLRenderer 在 Django-REST-Framework 中创建/放置?的主要内容,如果未能解决你的问题,请参考以下文章

[精选] Mysql分表与分库如何拆分,如何设计,如何使用

如果加入条件,我该如何解决。如果使用字符串连接,我如何使用

如何使用本机反应创建登录以及如何验证会话

如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]

如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?

如何使用laravel保存所有行数据每个行名或相等