I'm having a terrible time finding the right way to initialize the TemplateHTMLRenderer to render a form for creating an object. My API works perfectly for post (the DRF browsable API form) but that's not a frontend solution.

I can create a detail page using the docs and template packs: https://www.django-rest-framework.org/topics/html-and-forms/#rendering-forms. The docs are very clear on how to create an UPDATE form.

But I can't for the life of me initialize the same form to do the initial create.

I don't want to have to manually write forms to interact with the API for creating objects when the feature for auto-generated forms is there... but I'm completely at a loss.

From views.py the working update view:

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

It's a nested (OneToOne) serializer with custom update and create methods.

From 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

I have a template referencing the TemplateHTMLRenderer form and it works perfectly for updating when it can call a PK. I can't, however, create a view or URL that will pull the form for a create/post.

Models:

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

Additional view that is not working and does not include the 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)

Template:

{% 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 %}

URLS:

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 Urls work. Browseable API will allow create/post. path('inventory/licsoftware-detail//', views.LicensedSoftwareDetail.as_view()), works path('inventory/licsoftware-detail/', views.LicensedSoftwareList.as_view()), gives the error: