How to get single item in Django Rest Framework - python

I have a database with cars, and each car has an id. To get all cars I would go at this route api/cars/, now I am trying to implement getting a single car which has id 1 and this is my urls:
urlpatterns = [
path('api/cars/', CarsAPI.as_view()),
path('api/cars/:id/', CarAPI.as_view()),
path('api/tours/ongoing/', CarListOngoingAPI.as_view())
]
And this is my views for first path and second,
class CarsAPI(generics.ListCreateAPIView):
queryset = Car.objects.all()
serializer_class = CarsSerializer
# GET single car with id
class CarAPI(generics.RetrieveAPIView):
queryset = Car.objects.all()
serializer_class = CarsSerializer
class CarListOngoingAPI(generics.ListAPIView):
queryset = Car.objects.all()
serializer_class = CarsSerializer
And here is my Car model:
class Car(models.Model):
make = models.CharField(max_length=100, blank=True)
description = models.CharField(max_length=500, blank=True)
ongoing = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
First class returs a list of all car models. Now I need to find a way to implement two other types, one where an argument is passed in, in my case id api/cars/:id/ and the second api/cars/ongoing/, I should say that these are just hypothethetical cases, just for learning purposes and any help is greatly appreciated.

I'm quite bad at Django myself, from what what I've gathered you can simple do something like so:
def car_id(request, pk):
"""url: api/car/pk"""
try:
car = models.Car.objects.get(id=pk)
except models.Car.DoesNotExist:
# Whoopsie
return HttpResponseNotFound(
json.dumps({"ERR": f"car with id {id} not found"}),
content_type="application/json",
)
# Serialise your car or do something with it
return JsonResponse(CarSerializer(car).data)
(Assuming you have your api's urlpatterns defined like so)
urlpatterns = [
...
r"api/car/(?P<id>\d+)/", views.car_id
]

For the first part of my problem, I just had to fix the urls to:
urlpatterns = [
path('api/cars/', CarsAPI.as_view()),
path('api/cars/<int:pk>/', CarAPI.as_view()),
path('api/tours/ongoing/', CarListOngoingAPI.as_view())
]
And for the second, I learned how to filter a query set by overriding get_queryset()
class CarListOngoingAPI(generics.ListAPIView):
# queryset = Car.objects.all()
serializer_class = CarsSerializer
def get_queryset(self):
return Car.objects.filter(ongoing=True)

Related

Django + Django Rest Framework: get correct related objects on intermediate model

I have an intermediate model with the following fields:
class UserSkill(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
skill = models.ForeignKey(Skill, on_delete=models.CASCADE, related_name='user_skills')
disabled = models.BooleanField(default=False)
As you can see, it has two foreign keys, one to the auth user and one to another table called skill.
I am trying to get all Skills assigned to an specific user, so I do the following get_queryset in my ViewSet:
class AssignedSkillViewSet(viewsets.ModelViewSet):
queryset = Skill.objects.all()
serializer_class = AssignedSkillSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
user = self.request.user
return Skill.objects.filter(user_skills__user=user, user_skills_user__disabled=False))
Now, I also need to include the intermediate model information in the API, which I can access trough users_skills related name in DRF's Serializer, as follows:
class AssignedSkillSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Skill
fields = [
'id',
'url',
'title',
'description',
'user_skills',
]
But when I try to get that information it returns ALL user_skills related to the assigned skill, no matter if they are assigned to other users. I need the related model information only for that user and that skill.
For Example:
If I have a skill named Math, and a user named Maria
related_skills = Skill.objects.filter(user_skills__user=user, user_skills_user__disabled=False)).user_skills.all()
The above code will return:
[
<UserSkill: Math+Jenniffer>,
<UserSkill: Math+Gabriel>,
<UserSkill: Math+John>,
<UserSkill: Math+Maria>,
]
I only need to get the item <UserSkill: Math+Maria>. The list is not ordered in any way so getting the last item on the list does not work in all cases.
I know there is something I'm probably missing. I appreciate any help or clues you people can give me.
In this case you need to use a Prefetch..[Django-doc] object with a custom queryset, that uses the same filters as your main queryset like this:
from django.db.models import Prefetch
def get_queryset(self):
user = self.request.user
return Skill.objects.filter(
user_skills__user=user,
user_skills__user__disabled=False,
).prefetch_related(
"user_skills",
queryset=UserSkill.objects.filter(
user=user,
user__disabled=False,
)
)
I think that when you do the filter:
Skill.objects.filter(
user_skills__user=user, #condition_1
user_skills_user__disabled=False, #condition_2
).user_skills.all()
You already did a query related to the UserSkill model. Because the filter is done in the Skill model and the #condition_1 (user_skills__user=user) uses the information from the UserSkill model to filter by users. But when you do .user_skills.all() at the end of the query you are overring the filter with all the data from the UserSkill model.
To get a list of UserSkill instances from the filter you could try:
UserSkill.objects.filter(
user="Maria",
skill="Math",
)
Maybe this will help
serializers.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ['id', ...]
class UserSkillSerializer(serializers.ModelSerializer):
skill_detail = SkillSerializer(many=True)
class Meta:
model = UserSkill
fields = ['id', 'user', 'skill_detail']
views.py
class AssignedSkillViewSet(viewsets.ModelViewSet):
queryset = UserSkill.objects.all()
serializer_class = UserSkillSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
user = self.request.user
return UserSkill.objects.filter(user=user, disabled=False))

Django REST framework - filtering against query param with date Outside Views.py file

I created my "API" using REST framework, now trying to do filtering for it. That's how my models.py look for BookingStatement model.
class BookingStatement(BaseModel):
ticket_number = models.PositiveIntegerField(unique=True)
booking = models.OneToOneField(Booking, on_delete=models.PROTECT)
user_rate = AmountField()
agent_rate = AmountField()
total_amount = AmountField()
class Meta:
default_permissions = ()
def __str__(self):
return str(self.id)
Booking is One to One Key so the booking model has following Attributes.
class Booking(BaseModel):
bus_seat = models.ManyToManyField(Seat)
schedule = models.ForeignKey(Schedule, on_delete=models.PROTECT)
boarding_point = models.ForeignKey(
BoardingPoint,
on_delete=models.PROTECT,
null=True
)
remarks = models.JSONField(null=True, blank=True)
contact = PhoneNumberField(null=True)
booking_date_time = models.DateTimeField(auto_now_add=True)
class Meta:
default_permissions = ()
verbose_name = 'Booking'
verbose_name_plural = 'Bookings'
def __str__(self):
return '{}-{}'.format(self.user, self.customer_name)
I used generic ListAPIView in my views.py as following.
class BusCompanyTicketDetailView(generics.ListAPIView, BusCompanyMixin):
serializer_class = serializers.TicketDetailResponseSerializer
def get_queryset(self):
travel_date = (int(self.request.query_params.get('booking_date')))
print(booking_date)
return usecases.ListBusCompanyTicketUseCase(date=#'what should i pass?'#).execute()
I use usecases.py to filter booking_date_time with url as following.
http://127.0.0.1:8000/api/v1/ticket/list?booking_date=2021-1-29
So my usecase file to filter the Booking time is as following.
class ListBusCompanyTicketUseCase(BaseUseCase):
def __init__(self, date:datetime):
self._date = datetime
def execute(self):
self._factory()
return self._booking_statements
def _factory(self):
self._booking_statements = BookingStatement.objects.filter(booking__booking_date_time=?? need to get date from views.)
Problem is I don't know to how to get query params from url in my usecases to filter with booking date any help will be very helpful.
you should use django-filter to implement filtering on the viewsets. It's a bit of knowledge you have to build up, but once you understand it, you can do a lot of complex filtering logic with it. Trying to implement a filtering system yourself is always more difficult in the long run. For starting point, check out the official documentation: https://www.django-rest-framework.org/api-guide/filtering/. For the filter, check out the documentation on DRF integration: https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html.

Django REST Framework customize update method

My application has the following structure:
models.py
class EventHost(models.Model):
hostid = models.ForeignKey(Host, on_delete=models.PROTECT)
eventid = models.ForeignKey(Event, on_delete=models.CASCADE)
someparam = models.CharField(max_length=100, blank=True, null=True)
...
class Meta:
unique_together = ("hostid", "event")
class Host(models.Model):
hostid = models.IntegerField(primary_key=True)
hostname = models.CharField(max_length=100)
...
class Event(models.Model):
eventid = models.AutoField(primary_key=True)
eventname = models.CharField(max_length=100)
...
hosts = models.ManyToManyField(Host, through='EventHost')
views.py
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.order_by('-date')
serializer_class = EventSerializer
class HostViewSet(viewsets.ModelViewSet):
queryset = Host.objects.order_by('-hostid')
serializer_class = HostSerializer
class EventHostViewSet(viewsets.ModelViewSet):
queryset = EventHost.objects.all()
serializer_class = EventHostSerializer
Currently to update EventHost table I'm doing http put providing id (which is primary key) in my url .
I'd like to be able to update EventHost providing hostname and eventname (which will be passed in url) instead of id.
Using SQL it would look like this:
update eventhost set someparam='somevalue' from eventhost as a, event as b, host as c where b.id = a.eventid and c.hostid = a.hostid and b.eventname='myevent' and c.hostname='myhost';
From documentation I understood that I would need to modify the default update method for the viewset or/and modify queryset. Any ideas how should it be achieved?
You can override get_object method and use your parameters provided from url like that:
class EventHostViewSet(viewsets.ModelViewSet):
queryset = EventHost.objects.all()
serializer_class = EventHostSerializer
def get_object(self):
return EventHost.objects.get(hostid=self.kwargs['hostid'],eventid=self.kwargs['eventid'])
In this way, you must manage if there is no record with this query scenario, as custom
Assuming that you have properly constructed URL.
Edited EventHostViewSet.get_object method:
class EventHostViewSet(viewsets.ModelViewSet):
...
lookup_field = 'eventname'
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
filter_kwargs = {'hostid__hostname': self.kwargs.get('hostname'),
'eventid__eventname': self.kwargs.get('eventname')}
obj = get_object_or_404(queryset, **filter_kwargs)
self.check_object_permissions(self.request, obj)
return obj
EventHostViewSet registration:
router.register(rf'event-hosts/(?P<hostname>[.]+)', EventViewSet)
Some comments about your problem:
You will have problem when in your system will exist two EventHost with same hostid__hostname and eventid__event because queryset get method should only return ONE record
in DRF PUT method needs all fields to be provided in request data, if you want to update selected fields you should use PATCH method or override update viewset method (set partial to True)
EDITED AGAIN:
This is really bad design, you should not do this like that (somehow you should use id / maybe #action decorator to construct specific url for updating like that).

DRF-Filtering with multiple query params and foreign keys

I'm new to Django and I'm trying to create a route that can be called to retrieve an array of vehicles from my database, but I want the user to be able to provide multiple query params in the url (something like: http://127.0.0.1:8000/vehicles/?year=2020&make=Toyota). The problem that I have come across is that my vehicle model includes references to foreign keys for the make and the v_model (so named to avoid conflict with the Django "model"). I have a solution that doesn't seem very graceful. The fact that I have three nested conditional statements for each search field makes me suspicious. I tried using "filters.SearchFilter" but I was only able to provide a single value on which to base the search. So a request like this: http://127.0.0.1:8000/vehicles/?search=2020&search=Toyota would only search for vehicles with a make of "Toyota", ignoring the "year" parameter.
Is there some other way to do this that is cleaner or more "Django-approved"?
Here is my code:
models.py:
class Make(models.Model):
name = models.CharField(max_length=200, unique=True)
def __str__(self):
return self.name
class VModel(models.Model):
name = models.CharField(max_length=200, unique=True)
make = models.ForeignKey(Make, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Vehicle(models.Model):
make = models.ForeignKey(Make, on_delete=models.CASCADE)
v_model = models.ForeignKey(VModel, on_delete=models.CASCADE)
year = models.CharField(max_length=5)
def __str__(self):
return self.year + " " + self.v_model.name
views.py:
Here is my attempt with filters.SearchFilter:
queryset = Vehicle.objects.all()
serializer_class = VehicleSerializer
filter_backends = [filters.SearchFilter]
search_fields = ['year', 'v_model__name','make__name']
And here is my "working" solution that seems hacky:
(NOTE: I am using name__icontains so that if a user enters "Toyot" it will still get all cars with a make of Toyota).
class VehicleListViewSet(viewsets.ModelViewSet):
serializer_class = VehicleSerializer
queryset = Vehicle.objects.all()
pagination_class = CustomPagination
def get_queryset(self):
qs = super().get_queryset()
selected_make = self.request.query_params.get('make', None)
if selected_make:
try:
found_make = Make.objects.get(name__icontains=selected_make)
except:
return []
if found_make:
if found_make.id:
qs = qs.filter(make=found_make.id)
selected_v_model = self.request.query_params.get('v_model', None)
if selected_v_model:
try:
found_v_model = VModel.objects.get(name__icontains=selected_v_model)
except:
return []
if found_v_model:
if found_v_model.id:
qs = qs.filter(v_model=found_v_model.id)
selected_year = self.request.query_params.get('year', None)
if selected_year:
qs = qs.filter(year=selected_year)
return qs
You shouldn't be using filters.SearchFilter. Instead, use a filterset_fields attribute in your ViewSet, like the example from this section of the documentation.
Your viewset would be like this:
class VehicleListViewSet(viewsets.ModelViewSet):
serializer_class = VehicleSerializer
queryset = Vehicle.objects.all()
pagination_class = CustomPagination
filter_backends = [DjangoFilterBackend]
filterset_fields = ['year', 'v_model__name','make__name']
(Note there's no get_queryset override) You'll be able to query your API like this:
http://127.0.0.1:8000/vehicles/?year=2020&make__name=Toyota&v_model__name=Corolla

invalid literal for int() with base ten in listAPI view django rest framework

I am using a view in django rest framework. In this view it takes an argument city to then fetch a list a neighborhoods in that city.
the example of the url looks like this:
http://127.0.0.1:8000/api/neighborhood-list/chicago/
the url code looks like this:
url(r'neighborhood-list/(?P<city>[a-zA-Z]+)/', VenueFilterOptionsView.as_view()),
the view:
class NeighborhoodListView(generics.ListAPIView):
lookup_field = 'city'
def list(self, request, city):
self.city = city
queryset = Neighborhood.objects.filter(city=self.city)
serializer = NeighborhoodSerializer(queryset, many=True)
the serializer:
class NeighborhoodSerializer(serializers.ModelSerializer):
class Meta:
model = Neighborhood
fields = 'neighborhood'
the model:
class Neighborhood(models.Model):
city = models.ForeignKey(City, null=True)
neighborhood = models.CharField(max_length=150, null=False)
what I don't understand is I set the lookup field to city, unless that only works for instances not lists? And even so I am using the listAPIView generic
the exception location is here:
/home/rickus/211hospitality/suitsandtables/backend/venv/local/lib/python2.7/site-packages/django/db/models/fields/__init__.py in get_prep_value, line 966
and the code on line 966 is the following:
def get_prep_value(self, value):
value = super(AutoField, self).get_prep_value(value)
if value is None:
return None
return int(value)
the return value of this method in the init folder being referenced by the stack trace is being cast as an int every time. SO I guess now the question is how do we override this nonsense or work around it.
so now I am working my way back trying to figure out what is going on.
anyone have any ideas?
Update - My original answer was incorrect. List view doesn't actually work with the lookup_field and lookup_url_kwarg attributes, those attributes are used by Rest Frameworks DetailView in the get_object(self) method to retrieve a single instance using those lookup fields.
I've updated the answer so it overrides the get_queryset(self) method to return the correctly filtered list. This is how ListView should be customised.
It looks like you haven't defined your ListView properly. The problem seems to be that you are trying to filter on the Cities Primary Key, which is an integer field, using a string that can't be parsed as an integer. I'll write up how I think your view should look presuming your trying to do your filtering based on some field on the City model.
# models.py
class City(models.Model):
name = models.CharField(max_length=32)
class Neighbourhood(models.Model):
city = models.ForeignKey(City)
# views.py
class NeighbourhoodListView(generics.ListAPIView):
queryset = Neighbourhood.objects.all()
serializer_class = NeighbourhoodSerializer
def get_queryset(self):
return self.queryset.filter(city__name=self.kwargs.get('city')
# urls.py
urlpatterns = [
url(
r'^neighbourhood-list/(?P<city>[a-zA-Z]+)/$',
NeighbourhoodListView.as_view(),
name='neighbourhood-list',
)
]
This will filter your Neighbourhoods by the Cities name. If you want to filter Neighbourhoods by the cities Primary Key, then you should use:
# views.py
class NeighbourhoodListView(generics.ListAPIView):
queryset = Neighbourhood.objects.all()
serializer_class = NeighbourhoodSerializer
def get_queryset(self):
return self.queryset.filter(city=self.kwargs.get('city'))
# urls.py
urlpatterns = [
url(
r'^neighbourhood-list/(?P<city>\d+)/$',
NeighbourhoodListView.as_view(),
name='neighbourhood-list',
)
]
This fixes the view and url's to filter Neighbourhoods by the Cities Primary Key. This would be more performant because it doesn't need to perform a join between City and Neighbourhood.

Categories