drf-spectacular issue with filterset_fields - django

I am trying to implement drf-spectacular to an existing Django REST API.
However I am getting the following error, when attempting to run
./manage.py spectacular --file schema.yml
Error:
python3.7/site-packages/django_filters/filterset.py", line 352, in get_filters
"%s" % ', '.join(undefined)
TypeError: 'Meta.fields' contains fields that are not defined on this FilterSet: client, tenant_id, subtenant_id, data_stream_id
The filters do work, but don't seem to play nicely with the drf-spectacular lib. Can anyone please advise on how this might be solved?
Specs as follows:
Python 3.7.2
Django 3.0.2
django-filter 2.2.0
django-rest-framework 0.1.0
djangorestframework 3.12.1
drf-spectacular 0.12.0
Viewset example:
class subModelViewSet(viewsets.ModelViewSet):
"""Standard ViewSet for the DataStream Model."""
queryset = DataStream.objects.all()
serializer_class = DataStreamSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ('client', 'tenant_id', 'subtenant_id', 'data_stream_id',)
Serializer example:
class DataStreamSerializer(serializers.ModelSerializer):
"""Class to validate an uploaded DataStream."""
class Meta:
"""Nested Meta Class."""
model = DataStream
fields = '__all__'

Turns out it was a Viewset that was trying to apply filterset_fields on a subModel that did not directly contain those fields, but rather referenced by ForeignKey on another Parent Model. While I thought these filters were working as expected, I think the actual filtering on those fields was being performed by middleware in this case.
Removing the fields from the Viewset allows the schema to be generated with no issue. I am able to access the /docs page fully rendered and /schema endpoint working. Very impressed with drf-spectacular thus far.
example model:
class DataSteam(models.Model):
"""Class for DataSteam Model."""
client = models.CharField(max_length=200)
tenant_id = models.CharField(max_length=200)
subtenant_id = models.CharField(max_length=200)
data_stream_id = models.CharField(max_length=200)
class subModel(models.Parent):
"""Class for storing linked datastream records."""
ds = models.ForeignKey(DataStream, on_delete=models.CASCADE)

Related

How to set nullable field to None using django rest frameworks serializers

I have a simple view with which I want to be able to set a nullable TimeField to None:
class Device(models.Model):
alarm_push = models.TimeField(null=True, blank=True)
class DeviceSettingsSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ('alarm_push',)
class DeviceSettingsView(RetrieveUpdateAPIView):
serializer_class = DeviceSettingsSerializer
lookup_field = 'uuid'
def get_queryset(self):
return Device.objects.all()
But if I try to PATCH data like {'alarm_push': None} I get an error like {"alarm_push":["Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]]."]}
How can I set alarm_push to None?
As your Serializer is a ModelSerializer DRF will use a TimeField() for your alarm_push model attribute. When you checkout the sourcecode of the DRF Timefield https://github.com/encode/django-rest-framework/blob/master/rest_framework/fields.py#L1278 you can see that to_internal_value is raising your error when every attempt of parsing the value failes.
So to have your TimeField be empty you should patch {"alarm_push": ""} with an empty string to represent an empty state.

django rest framework access item by lookup field instead of pk 3.4 DRF

I need to have lookup field in order my frontend sends email which should be deleted but I get item not found. I've researched a lot about this problem but I can't figure out which DRF version what supports.
class EmailReminderSerializer(serializers.ModelSerializer):
city = serializers.CharField(max_length=255)
url = serializers.HyperlinkedIdentityField(
view_name='web:email_reminder-detail',
)
class Meta:
model = EmailReminder
fields = '__all__'
extra_kwargs = {
'url': {'lookup_field': 'email'}
}
Now I have url but it points to instance pk, not by my desired lookup field.
Any suggestions of how it works in 3.4 version or do you have any other solutions to some lower version >=3.0?
Oh okay, I got it. For serialized models you only need lookup_field in your view but for hyperlinked serialized models you need extra_kwargs in serializers plus lookup field in views. Hope it helps someone
You should modify the lookup field in your view instead. As shown in DRF docs, you can do the following.
in views.py
from rest_framework import viewsets
class EmailReminderViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
lookup_field = 'email'

Django Rest Framework Generic Relationships and ViewSets

I have a model Comment that can go on either Project or Task.
class Comment(BaseCommentModel):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(
ContentType,
verbose_name=_('content type'),
related_name="contenttype_set_for_%(class)s"
)
object_pk = models.TextField(_('object ID'))
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
Project and Task have the field set up as:
comments = GenericRelation(Comment)
Comments can be created on either Projects or Tasks so there should be a viewset for each:
class ProjectCommentViewSet(viewsets.ViewSet):
class TaskCommentViewSet(viewsets.ViewSet):
And those would display the comments related to each model.
But what I don't understand is:
How do I set up the create/update/delete in a viewset for the Comment model so that the comment is created with the correct relationship?
How do I filter inside the viewsets to display comments related to that model? I can't use select_related because the Comment doesn't have a Project or Task field.
How do I write the HyperlinkedModelSerializers for these relationships? Do I need to add a HyperlinkedIdentityField to CommentSerializer() and then HyperlinkedRelatedFields to the User, Project, and Task Serializers? Or how do I set this up?
Thanks for any help provided, I could really use some direction here.
I'm having trouble understanding how the relationships on the models translate to the serializers and viewsets. And also how to handle the relationships when using generic relations.
The key is to use PrimaryKeyRelatedField. This will return an list of id's, which is what you would use for a create/update for a Project model instance with related Comment records.
Other than that, GenericRelation behaves just like other ORM relationships within django-rest-framework.
In the ViewSet Serializer define it like so:
from rest_framework import viewsets, serializers
class ProjectCommentViewSet(viewsets.ViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
class ProjectSerializer(serializers.ModelSerializer):
comments = serializers.PrimaryKeyRelatedField(
queryset=Comment.objects.all(), many=True, required=False)
class Meta:
model = Project
fields = ('id', 'etc...', 'comments',)

how to troubleshoot DRF router duplicate urls

I am trying to set a series of Django Rest Framework URLs.
Below is my Serializer/ViewSet makeup
class ModelSerializer(serializers.HyperlinkedModelSerializer):
schemas = SchemaSerializer(many=True, read_only=True)
class Meta:
model = dbModels
fields = ('ModelName', 'pk', 'schemas')
class ModelViewSet(viewsets.ModelViewSet):
queryset = dbModels.objects.all()
serializer_class = ModelSerializer
class ModelListSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = dbModels
fields = ('ModelName', 'pk')
class ModelListViewSet(viewsets.ModelViewSet):
queryset = dbModels.objects.all()
serializer_class = ModelListSerializer
Here is my Router List:
from datagenerator.serializer import UserViewSet, \
ModelViewSet, ModelListViewSet
router = routers.DefaultRouter()
router.register(r'models', ModelViewSet)
router.register(r'modellist', ModelListViewSet)
However, when I'm running The localhost webserver, the DRF Front end shows this:
"models": "http://localhost:8000/datamaker/api/modellist/",
"modellist": "http://localhost:8000/datamaker/api/modellist/",
How do I stop this?
I need models to go to models and modellist to go to modellist.
Thanks much...
Use the base_name argument:
router.register(r'models', ModelViewSet, base_name='models')
router.register(r'modellist', ModelListViewSet, base_name='modellist')
Since your serializers share the same data model, DRF might get stuck trying to automatically discover the url naming pattern. So it's better in this case to explicitly set the base_name.
If you're using a newer version of Django Rest Framework, you'll need to use basename='models' instead of base_name='model'.

How to get GeoJSON response in Django REST framework gis

I am trying to get GeoJSON response using django rest framework but facing issue
argument of type 'NoneType' is not iterable
This is my code
class NewPark(models.Model):
name = models.CharField(max_length=256)
geometry = models.GeometryField(srid=3857, null=True, blank=True)
objects = models.GeoManager()
class Meta:
db_table = u'new_park'
def __unicode__(self):
return '%s' % self.name
class NewParkSerializer(GeoFeatureModelSerializer):
class Meta:
model = NewPark
geo_field = "geometry"
fields = ('id', 'name', 'geometry')
class NewParkViewSet(viewsets.ModelViewSet):
def get_queryset(self):
queryset = NewPark.objects.all()
return queryset
When i change serialize type to 'erializers.GeoModelSerializer' then it is working, but i want GEOJSON response
I have searched about GeoFeatureModelSerializer but cannot find any example geo_field = "geometry". All example are about geo_field = "point"
Please help me to figure out this issue?
You might only have the above error in the browsable API, because the default djanog-rest-framework html template renderer does not work with the GeoJson format.
To test if that is the case, try to call your api endpoint requesting it in json format, i.e. /api/newpark.json or equivalently /api/newpark?format=json. This should show you geojson data, if it does your backend API is fine, and the problem is the browsable html form.
If you want a browsable API for your GeoJson endpoint, then you might need to change the html template used to render your API end point.