how to troubleshoot DRF router duplicate urls - django

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

Related

How can I encrypt url in Django Rest Framework?

I found a documentation since it is not working with python updated version so I am having this problem. I want to prevent scrapping from my application. There are some api where I am passing sensitive data and my api endpoing is like localhost:8000/api/products/1 but I want this url to be like
localhost:8000/api/products/dheudhuehdeidiwf4yfg4gfy4yf4f4fu4f84j4i this. So which procedure should I follow here?
You can use uuid as another unique key in your model.
import uuid
class Product(models.Model):
uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
# other fields ...
For the serializers, you'll have to manually set them like:
class ProductSerializer(serializers.Serializer):
uuid = serializers.UUIDField(format="hex", read_only=True)
# other fields ...
class Meta:
model = Product
fields = [
"uuid",
# other fields ...
]
For the views, I'm assuming you are using ModelViewSet, so you can set the uuid as the lookup field like:
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
lookup_field = "uuid"
One way to go about making your sensitive ids urlsafe would be to use urlsafe_base64_encode from django.utils.http. You could return encrypted ids along with your response to the frontend using:
uidb64 = urlsafe_base64_encode(force_bytes(model_name.pk))
the frontend can then persist the encrypted ids and when request are made to your endpoints using those ids, you then decrypt them using smart_str from django.utils.encoding like this:
model_name_pk = smart_str(urlsafe_base64_decode(uidb64))
assuming your endpoints looks something like this 'api/an-interesting-route/<uidb64>'
This approach is more useful with GET endpoints that do not just return the model directly but include some amount of processing of the id before a response is returned.

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

Why can't Django REST Framework's HyperlinkedModelSerializer form URL?

New to DRF and everything works as long as I don't include 'url' in fields. Here's what I've got:
Serializer:
class TaskSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Task
fields = ('pk', 'short_desc', 'scheduled_date')
View Set:
class TaskViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Task.objects.all().order_by('scheduled_date')
serializer_class = TaskSerializer
URLs:
router = routers.DefaultRouter()
router.register(r'tasks', views.TaskViewSet)
urlpatterns = [
[... bunch of non-REST URLs]
# REST API
url(r'^', include(router.urls)),
At runtime, printing router.urls gives me:
<RegexURLPattern api-root ^$>
<RegexURLPattern api-root ^\.(?P<format>[a-z0-9]+)/?$>
<RegexURLPattern task-list ^tasks/$>
<RegexURLPattern task-list ^tasks\.(?P<format>[a-z0-9]+)/?$>
<RegexURLPattern task-detail ^tasks/(?P<pk>[^/.]+)/$>
<RegexURLPattern task-detail ^tasks/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>
Both [...]/tasks/ and [...]/tasks/123/ work if I type them into my browser, which leads me to believe that task-list and task-detail views do, in fact exist.
Now I introduce a problem by adding 'url' to the serializer:
class TaskSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Task
fields = ('url', 'pk', 'short_desc', 'scheduled_date')
After adding 'url' to fields, I get the following error:
Could not resolve URL for hyperlinked relationship using view name
"task-detail". You may have failed to include the related model in
your API, or incorrectly configured the lookup_field attribute on
this field.
The DRF docs say:
There needs to be a way of determining which views should be used for
hyperlinking to model instances. By default hyperlinks are expected to
correspond to a view name that matches the style '{model_name}-detail',
and looks up the instance by a pk keyword argument.
Since I've verified that task-detail exists and that the corresponding URL [...]/tasks/123/ works, I can't for the life of me figure out why DRF can't form the URL. Any ideas?
Inspired by clues revealed by Kunkka's answer, I have a solution that looks like this:
class TaskSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="task:task-detail")
class Meta:
model = Task
fields = ('url', 'pk', 'short_desc', 'scheduled_date')
I.e. I've added an url = [...] line to the serializer I originally posted in my question. This solves the lookup problem which was presumably caused by DRF not knowing that 'task-detail' is actually in the 'task' namespace.
Any better solutions?
Can you try this?
class TaskSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.SerializerMethodField()
class Meta:
model = Task
fields = ('pk','url', 'short_desc', 'scheduled_date')
def get_url(self,obj):
request = self.context['request']
return = {'self':reverse('task-detail',kwargs={'pk':obj.pk},request=request)}

Get relationship attributes with Django's REST API

There are two tables user, phone which are linked by an intermediate table owner. Here the goal is to use Rest API to get all phones from a specific user,
http://127.0.0.1/users/alice/phones/.
I use ModelSerializer as serializer and ViewSet as view. Please let me know how to get this done? I have no idea how to route /users/user_name/phones/ to get phones from a specific user.
Thanks.
Code snippet:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model=User
class PhoneSerializer(serializers.ModelSerializer):
class Meta:
model=Phone
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model=Owner
depth=1
// views
class UserViewSet(viewsets.ModelViewSet):
queryset=User.objects.all()
serializer_class=UserSerializer
class PhoneViewSet(viewsets.ModelViewSet):
queryset=Phone.objects.all()
serializer_class=PhoneSerializer
....
I'd suggest you to create a filter.
It will be something like this:
1) Create filter (Make sure that django-filter is installed.):
# filters.py
import django_filters
class PhoneFilter(django_filters.FilterSet):
user = django_filters.Filter(name="user__user_name")
class Meta:
model = Phone
fields = ('user',)
2) Add filter to your ViewSet:
# views.py
class PhoneViewSet(viewsets.ModelViewSet):
queryset=Phone.objects.all()
serializer_class=PhoneSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = filters.PhoneFilter
And now you may use this url: /phones/?user=user_name.
Use #detail_route to route URL to your function, e.g.,
#detail_route(methods=['get'])
def phones(self, request, pk=None):
pass
http://127.0.0.1/users/alice/phones will work!