Django: DetailView and two slug fields - python

My database model has different organizers who can have several events. I now want to filter my DetailView down to organizer and then a specific event. My solution is that one here, but I still have in mind, that there shouldn't be two slug fields in the get_object method. Is there another approach to what I am trying to do?
views.py
class EventDetailView(DetailView):
context_object_name = 'event'
def get_object(self):
organiser = self.kwargs.get('organiser')
event = self.kwargs.get('event')
queryset = Event.objects.filter(organiser__slug=organiser)
return get_object_or_404(queryset, slug=event)
urls.py
urlpatterns = [
path(
'<slug:organiser>/<slug:event>/',
EventDetailView.as_view(),
name='event'
),
]

Related

How do I get a hyperlinked foreign key in Django REST Framework serializer?

I am trying to serialize a model in my DRF project. The model contains 2 foreign key fields and some simple data type fields. One of the foreign key fields is to my CustomUser model and this field appears as a hyperlink in my API output which is expected. The other foreign key field is to a different model (shown below). I have been cycling through the same few errors over the past several hours as I've tried various solutions recommended in other StackOverflow threads.
Could someone please show me how to get this second foreign key to appear as a link in my API output?
When my code files appear as they do below, I get this error when trying to access the localhost:8000/trade/create/ and localhost:8000/trader-accounts/ URLs:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "traderaccount-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
Of course, I Googled this error messages and portions thereof several times, and went down several DRF rabbit holes trying various things.
trades/models.py
class Trade(models.Model):
trade_owner = models.ForeignKey(CustomUser, related_name='owners', on_delete=models.RESTRICT)
trade_account = models.ForeignKey(TraderAccount, related_name='trades', on_delete=models.RESTRICT)
trader_account/models.py
class TraderAccount(models.Model):
account_owner = models.ForeignKey(
CustomUser, on_delete=models.CASCADE,
)
account_name = models.CharField(
max_length=200, blank=True, verbose_name='Account Name'
)
api/serializers.py
class TraderAccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TraderAccount
fields = ['account_owner', 'account_name',]
class TradeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Trade
fields = ['trade_owner', 'trade_account', 'magic_number',]
api/views.py
class TraderAccountView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
class TradeCreateView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = Trade.objects.all()
serializer_class = TradeSerializer
api/urls.py
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('', include(router.urls)),
path('trader-accounts/', views.TraderAccountView.as_view(), name='trader-accounts'),
path('trade/create/', views.TradeCreateView.as_view(), name='trade-create'),
When I remove 'trader_account' from api.serializers.TradeSerializer.Meta.fields I can successfully access both the localhost:8000/trade/create/ endpoint and the localhost:8000/trader-accounts/ endpoint.
api/serializers.py
# Omit TraderAccountSerializer class for brevity in this example
class TradeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Trade
fields = ['trade_owner', 'magic_number',] # Removed 'trader_account'
I've been looking at this so long now I can't find the mistake. Really appreciate the help!
Based on this api/views.py you have a TraderAccountView(generics.ListCreateAPIView).
You should rename that to TraderAccountList(generics.ListCreateAPIView) and then add an extra view for the details; TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView)
api/views.py is now:
class TraderAccountList(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
class TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
Your urlpatterns should now be
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('', include(router.urls)),
path('trader-accounts/', views.TraderAccountList.as_view(), name='trader-accounts'),
path('trader-accounts/<int:pk>/', views.TraderAccountDetail.as_view()),
]
And remove TradeCreate. As you are now building a RESTful API. to create a trade you do a POST request to the list view to create a new trade.
On the list view you can do GET to get a list of all trades, or a POST to create a new trade.
On the detail view, you can do GET to get a specific trade, or a PUT or PATCH to update, or DELETE to delete the trade.
Have you tried making a route for the account in the same manner as user?
The error message you included says that there is no route with the expected name.
According to the documentation at https://www.django-rest-framework.org/api-guide/serializers/#how-hyperlinked-views-are-determined, for the automapping to work you need a view with the name account-detail which takes 1 integer route parameter.
Hyperlinked serializers are fiddly, as they say, so pay close attention to docs. If you change the view name, you can override it in the serializer.
account = serializers.HyperlinkedRelatedField(
view_name='account-detail', # or whatever name you give it
...
)
Many thanks to Gabbeh for pointing me in the right direction.
I renamed TraderAccountView(generics.ListCreateAPIView) to TraderAccountList(viewsets.ModelViewSet) and added this view to api/views.py:
class TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
The api/urls.py file is now:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
router.register(r'trader-accounts', views.TraderAccountList)
And everything works!

How can I create a view that retrieves all objects with matching primary key in url?

I have two models that form a one-to-many relationship. One pallet has multiple images associated with it.
models.py
class Pallet(models.Model):
pallet_id = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.pallet_id)
class Meta:
ordering = ('pallet_id',)
class Image(models.Model):
created = models.DateTimeField(auto_now_add=True)
pallet = models.ForeignKey(Pallet, on_delete=models.CASCADE)
image = models.FileField(upload_to="")
def __str__(self):
return str(self.created)
I'm trying to create a view where I get all images associated with a particular pallet_id from the url.
serializers.py
class ImageSerializer(serializers.HyperlinkedModelSerializer):
pallet = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Image
fields = '__all__'
class PalletSerializer(serializers.ModelSerializer):
class Meta:
model = Pallet
fields = '__all__'
urls.py
urlpatterns = [
url(r'^pallets/', include([
url(r'^(?P<pk>[0-9]+)/$', views.PalletDetail.as_view(), name='pallet-detail'),
])),
]
I think the issue is in the views.py with the PalletDetail class. I am confused on how to write the view based on the primary key from the URL. I've tried to use **kwargs['pk'] but does using this make it a function-based view? If so, would it be bad form to mix class-based and function-based views? How can I get similar behavior from class-based views?
I'm really struggling with the views here:
views.py
class PalletDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Image.objects.prefetch_related('pallet').all()
serializer_class = ImageSerializer
In Function based view
pass pk parameter
example:
def Example_function(request, pk):
queryset = Model.objects.filter(id=pk)
In Class-Based views
get url parameter
pk = self.kwargs.get('pk')
queryset = Model.objects.filter(id=pk)
You're going about it the wrong way. You want to return a list of images belonging to the pallet so what you need is a list view for the images and not a pallet detail view. The url should look like this:
/pallets/<pallet_id>/images/
In this way the url is self-descriptive and by REST standards you can easily see that you are requesting for images belonging to a pallet and not the pallet itself
In urls.py
urlpatterns = [
url(r'^pallets/', include([
url(r'^(?P<pallet_pk>[0-9]+)/images/$', views.PalletImagesListView.as_view(), name='pallet-images-list'),
])),
]
In views, you have to overide the get_queryset() method, so that it only returns the images for the pallet specified
class PalletImagesListView(generics.ListAPIView):
serializer_class = ImageSerializer
def get_queryset(self):
return Image.objects.prefetch_related('pallet').filter(pallet__pk=self.kwargs.get('pallet_pk'))
So now you can request the first pallet's images with /pallets/1/images/
You are using the serializer and view in a wrong way.
The problem with yours PalletDetail view is that you are using the wrong queryset. The queryset should return Pallet objects, since the lookup will be on that queryset (default lookup field is 'pk'). Also, you will then need to use the serializer for Pallet since that is supposed to handle the data for the Pallet object.
class PalletDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Pallet.objects.all()
serializer_class = PalletSerializer
Have you tried filter_by() method
queryset = Model.query.filter_by(id=pk).first()

HyperlinkedRelatedField DRF does not work with a ViewSet

I have these models:
class ExamSheet (models.Model):
pass
class Exam(models.Model):
exam_sheet = models.ForeignKey('myapp.ExamSheet',
related_name='exams',
)
Serializer:
class ExamBaseSerializer(serializers.ModelSerializer):
exam_sheet = serializers.HyperlinkedRelatedField(queryset=ExamSheet.objects.all(), view_name='examsheet-detail')
class Meta:
model = Exam
fields = ('id', 'user', 'exam_sheet', )
read_only_fields = ('id', 'user',)
ViewSets:
class ExamViewSet(MultiSerializerViewSet):
queryset = Exam.objects.all()
class ExamSheetViewSet(MultiSerializerViewSet):
queryset = ExamSheet.objects.all()
Routes:
app_name = 'exams_api'
router = DefaultRouter()
router.register(r'exams', views.ExamViewSet)
router.register(r'exams_sheets', views.ExamSheetViewSet)
urlpatterns = []
urlpatterns += router.urls
Global app urls:
urlpatterns = [
path('api/', include('exams_api.urls')),
]
GenericViewSet:
class MultiSerializerViewSet(viewsets.ModelViewSet):
serializers = {
'default': None,
}
def get_serializer_class(self):
return self.serializers.get(self.action, self.serializers['default'])
But this throws me an error:
ImproperlyConfigured at /api/exams/
Could not resolve URL for hyperlinked relationship using view name
"examsheet-detail". You may have failed to include the related model
in your API, or incorrectly configured the lookup_field attribute on
this field.
How can I use HyperlinkedRelatedField to show a link to a related model in my serializer?
The view_name you set for the hyperlink field view_name='examsheet-detail, do you have a view named examsheet-detail in your urlconf(mostly urls.py) for that app? If not, after include(views.someview), add ,name='examsheet-detail' as a third parameter to path.
Have you set app_name in the specific app's urlsconf? Removing it solved the issue for me.
Make sure <int:pk> in the url path for that view and not <int:id> or something else. Else, set lookup_field='id' cos the default is pk.
Make sure that view takes in the pk parameter.

Django Rest Framework different format response format for same URL and HTTP method

I working on an application with uses Django Rest Framework to handle queries and I use django-rest-framework-datatables plugin to help me handle pagination to datatables.
Works fine, but when I request for a single register it keeps bringing me a json format for datatables, like this:
{
"count": 1,
"next": null,
"previous": null,
"results": [{
"id": 1,
"name": "University of Passo Fundo",
"country": "Brazil"
}]
}
This is not a big issue, but I would prefer to receive just the result field. How can I defined two different response format for a same URL and same method, just checking request parameters in django rest framework?
Follow my code:
urls.py
router = routers.DefaultRouter()
router.register(r'institution', InstitutionViewSet, base_name='Institution')
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
# api
path('api/', include(router.urls)),
# views
url(r'^$', Home.as_view(), name='index'),
url(r'institution/', Institution.as_view(), name='institution'),
]
serializer.py
class InstitutionSerializer(serializers.ModelSerializer):
class Meta:
model = Institution
fields = '__all__'
datatables_always_serialize = ('id', 'name', 'country')
models.py
class Institution(models.Model):
id = models.AutoField(db_column='id', primary_key=True)
name = models.CharField(db_column='name', max_length=255, null=False)
country = models.CharField(db_column='country', max_length=255, null=False)
class Meta:
db_table = 'Institution'
managed = True
verbose_name = 'Institutions'
verbose_name_plural = 'Institutions'
ordering = ['id']
def __str__(self):
return self.name
views.py
class InstitutionViewSet(viewsets.ModelViewSet):
serializer_class = InstitutionSerializer
def get_queryset(self):
if 'type' in self.request.GET and self.request.GET['type'] == 'edit':
return Institution.objects.filter(id=self.request.GET['id'])
return Institution.objects.all().order_by('id')
First of all, that's the way Django render response for pagination.
So you can see the next or previous list of items based on the page.
And second you should override the list view of Django to be like this:
class InstituttionViewSet(viewsets.ModelViewSet):
serializer_class = InstitutionSerializer
pagination_class = None
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
In here, we are overriding the list method which is responsible to render a list of items API. So it will first get all items in queryset, then pass it to serializer to write it to a specific format, and at last return that lists in json for a response.
Also, remember I also set pagination_class=None So Django will not use pagination for APIs anymore.
I think You shouldn't override get_queryset view's method, this should solve your problem. The drf views handle default param by default via retrieve method. You can use your own pagination class to edit response schema.
You can override the method, get_serializer_class and use two different serializer depend of request parameter.

Django: DetailView and multiple slugs

I have an issue with my DetailView. I want to make sure both values are in the url string and then want to display the page. However I am always receiving this error here:
KeyError at /orders/ticket/ug2dc78agz-1/d04fkjmo37/
'order_reference'
views.py
class TicketView(DetailView):
model = Attendee
template_name = 'orders/ticket_view.html'
def get_queryset(self):
return Attendee.objects.filter(
order__order_reference=self.kwargs['order_reference'],
).filter(
access_key=self.kwargs['access_key'],
)
urls.py
urlpatterns = [
path(
'ticket/<slug:ticket_reference>/<slug:access_key>/',
TicketView.as_view(),
name='ticket'
),
]
You get the error because you are trying to access self.kwargs['order_reference'], but you don't use order_reference in the path().
Your path() uses,
'ticket/<slug:ticket_reference>/<slug:access_key>/'
therefore you can use self.kwargs['ticket_reference'] and self.kwargs['access_key'].
Since your path does not contain slug or pk, Django will not know how to fetch the object for the detail view. I would override get_object instead of get_queryset:
def get_object(self):
return get_object_or_404(
Attendee,
order__order_reference=self.kwargs['slug:ticket_reference'],
access_key=self.kwargs['access_key'],
)
You have ticket_reference url variable, but in view using order_reference. You should rename it:
class TicketView(DetailView):
model = Attendee
template_name = 'orders/ticket_view.html'
def get_queryset(self):
return Attendee.objects.filter(
order__order_reference=self.kwargs['ticket_reference'],
).filter(
access_key=self.kwargs['access_key'],
)

Categories

Resources