I've written some django code that allows me to request some data and returns it into a JSON format.
I request: http http://127.0.0.0.8000/api/club
[
{
"name": "Liverpool FC",
"abv": "LFC",
"stadium": "Anfield",
"manager": "Jurgen Klopp",
"trophies": "50"
},
{
"name": "Manchester City",
"abv": "MC",
"stadium": "Etihad",
"manager": "Manuel Pellegrini",
"trophies": "14"
},
{
"name": "Manchester United",
"abv": "MU",
"stadium": "Old Trafford",
"manager": "Louis Van Gaal",
"trophies": "46"
}
]
Is it possible to request only "Liverpool FC"'s data such that the request only returns. I can do this by http http://127.0.0.0.8000/api/club/1/ but I'd rather be able to type in a team name like http http://127.0.0.0.8000/api/club/liverpool/
{
"name": "Liverpool FC",
"abv": "LFC",
"stadium": "Anfield",
"manager": "Jurgen Klopp",
"trophies": "50"
}
Edit: Added two py files
views.py
# Create your views here.
class FishViewSet(viewsets.ModelViewSet):
# this fetches all the rows of data in the Fish table
queryset = Fish.objects.all()
serializer_class = FishSerializer
# Create your views here.
class TeamViewSet(viewsets.ModelViewSet):
# this fetches all the rows of data in the Fish table
queryset = Team.objects.all()
serializer_class = TeamSerializer
# Create your views here.
class ClubViewSet(viewsets.ModelViewSet):
# this fetches all the rows of data in the Fish table
queryset = Club.objects.all()
serializer_class = ClubSerializer
def getClub(request, club_name):
queryset = Club.objects.get(name=club_name)
models.py
class Fish(models.Model):
name = models.CharField(max_length=255)
created = models.DateTimeField('auto_now_add=True')
active = models.BooleanField()
class Team(models.Model):
starting = models.CharField(max_length=255)
captain = models.CharField(max_length=255)
value = models.CharField(max_length=255)
fixtures = models.CharField(max_length=255)
position = models.CharField(max_length=255)
class Club(models.Model):
name = models.CharField(max_length=255)
abv = models.CharField(max_length=255)
stadium = models.CharField(max_length=255)
manager = models.CharField(max_length=255)
trophies = models.CharField(max_length=255)
urls.py
router = routers.DefaultRouter()
#makes sure that the API endpoints work
router.register(r'api/fishes', views.FishViewSet)
router.register(r'api/teams', views.TeamViewSet)
router.register(r'api/club', views.ClubViewSet)
admin.autodiscover()
urlpatterns = router.urls
url(r'^admin/', include(admin.site.urls)),
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
url(r'^/api/club/(?P<club_name>[a-zA-Z]+)/$', ('getClub'))
I know a simple way by using regex
in the url.py add this regex
url(r'^/api/club/(?P<club_name>[a-zA-Z]+)/$', ....)
then in the views add a method will the club_name variable like this
from django.http import HttpResponse
def getclub(request, club_name):
teaminfo = ModelName.objects.get(name=club_name) # something like that
return HttpResponse(teaminfo)
the club_name variable will get the the string from the regex, you can use it in anyway you want.
Here's a simple good reference for this
https://docs.djangoproject.com/en/1.9/intro/tutorial03/
The issue is that you're using queryset = Team.objects.all() in your view. By nature, this implies that you'll get all of the objects.
I have a similar program, and I use urls.py like this-
...
url(r'^teams/(?P<incoming_team>[^/]+)/$', ('team_stats')),
url(r'^teams/', ('team_stats_all')),
in views it looks something like:
def team_stats(request, incoming_team):
queryset = Team.objects.get(name=incoming_team)
Obviously your existing view would be used for all. I'd also note you'll need some Try/Except handling, in case you have duplicate teams, or the team you request doesn't exist.
(In practice I don't actually split them out like this into separate views, but it gives you an idea of the logic flow)
Related
I have two serializers for my api to bring me data about company office locations.
class CountryFilialsSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CountryFilials
fields = ['name']
class FilialsSerializer(serializers.HyperlinkedModelSerializer):
country = CountryFilialsSerializer()
class Meta:
model = Filials
fields = ['country', 'name', 'subdomain_name', 'address']
CountryFilialsSerializer brings me the country name by a foreign key, and FilialsSerializer adds this data to other filial data.
A view that utilizes them both currently looks like this:
class FilialsViewSet(viewsets.ModelViewSet):
queryset = Filials.objects.all()
serializer_class = FilialsSerializer
It returns the response looking like that:
"results": [
{
"country": {
"name": "foo"
},
"name": "city1",
"subdomain_name": "subdomain1",
"address": "location1"
},
{
"country": {
"name": "foo"
},
"name": "city2",
"subdomain_name": "subdomain2",
"address": "location2"
},
But i need it to actually present the result like this:
[
{
"country": "foo",
"cities": [
{
"name": "city1",
"subdomain_name": "subdomain1",
"address": "location1"
},
{
"name": "city2",
"subdomain_name": "subdomain2",
"address": "location2"
},
]
},
]
Basically the same data, just grouped into a list by country.
I cant come up with a way to do this. As i realized, the serializer only receives one entry from the base at a time, and if i override its to_representation() method to include some formatting of the output, i wont be able to access multiple locations and group them by one country.
My next guess was that there should be similar way to work with the result directly from the view.
But i couldnt find anything about it in documentation (or maybe i didnt know what to even look for).
Found some info about actions, and update() method, but couldnt get how to utilize it for my goal.
Can you please suggest something? I dont get where and how do i put the logic into the view to be able to catch everything it shoves into the response and reorganize it.
You can have the list of countries with cities like this (this would require you to change the view the use CountryFilialsViewset)
class FilialsSerializer(serializers.ModelSerializer):
class Meta:
model = Filials
fields = ['name',]
class CountryFilialsSerializer(serializers.ModelSerializer):
filials = FilialsSerializer(many=True, read_only=True)
class Meta:
model = CountryFilials
fields = ['name', 'filials']
class CountryFilialsViewSet(viewsets.ModelViewSet):
queryset = CountryFilials.objects.prefetch_related('filials')
serializer_class = CountryFilialsSerializer
Or if you need to override the ModelViewSet you could do something like this:
class CountryFilialsViewSet(viewsets.ModelViewSet):
queryset = CountryFilials.objects.prefetch_related('filials_set')
serializer_class = CountryFilialsSerializer
def list(self, request):
response = [
{'country': country.name, 'cities': list(country.filials_set.values('name').all())} for country in CountryFilials.objects.all()
]
return Response(response)
EDIT: change filials to filials_set
Here is an example on how to change the fields names and data:
class FilialsSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField('get_url')
class Meta:
model = Filials
fields = ['name', 'url', 'address']
def get_url(self, obj):
# construct the url
return f'http://{obj.subdomain_name}.example.com/api/v1/cities/'
class CountryFilialsSerializer(serializers.ModelSerializer):
filials = FilialsSerializer(many=True, read_only=True, source='filials_set')
class Meta:
model = CountryFilials
fields = ['name', 'filials']
class CountryFilialsViewSet(viewsets.ModelViewSet):
queryset = CountryFilials.objects.prefetch_related('filials_set')
serializer_class = CountryFilialsSerializer
You would get the results you want using the CountryFilialsViewSet. for example in your urls.py you would have
router = routers.DefaultRouter()
router.register(r'filials', CountryFilialsViewSet),
urlpatterns = [
path('', include(router.urls)),
]
Calling http://api/filials would yield the response.
Let's say I have three models as:
class User(AppModel):
name = models.CharField(max_length=255)
class Business(AppModel):
owner = models.ForeignKey("User", related_name="businesses", on_delete=models.CASCADE)
legal_name = models.CharField(max_length=255)
class Invoice(AppModel):
business = models.ForeignKey("Business", related_name="invoices", on_delete=models.CASCADE)
amount = models.integerField()
As you can see, a user can have multiple businesses and a business can have multiple invoices.
My serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields= ('name')
class BusinessSerializer(serializers.ModelSerializer):
owner = UserSerializer(many=False)
class Meta:
model = Business
fields= ('owner','legal_name')
class InvoiceSerializer(serializers.ModelSerializer):
business= BusinessSerializer(many=False)
class Meta:
model = Invoice
fields= ('business','amount')
views.py:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class BusinessViewSet(viewsets.ModelViewSet):
queryset = Business.objects.all()
serializer_class = BusinessSerializer
class InvoiceViewSet(viewsets.ModelViewSet):
queryset = Invoice.objects.all()
serializer_class = InvoiceSerializer
urls.py:
router = DefaultRouter()
router.register('user', UserViewSet, base_name='users')
router.register('business', BusinessViewSet, base_name='businesses')
router.register('invoice', InvoiceViewSet, base_name='invoices')
urlpatterns = router.urls
http://example.com/api/user returns all users. Not a problem.
But the functionality I'm looking for is:
http://example.com/api/business/ returns
[
{
"legal_name": "1business",
"owner": 1,
},
{
"legal_name": "2business",
"owner": 1,
},]
http://example.com/api/business/1/ returns
{
"legal_name": "1business",
"owner": 1,
}
The above is ok. But I also need:
http://example.com/api/business/1/invoices/ should return
[
{
"business": 1,
"amount": 100,
},
{
"business": 1,
"amount": 999,
},]
As well I should be able to create update delete those invoices there.
Any Help? I'm new to django rest framework. The above classes are just a sample. Ignore errors.
You should use django decorators which are #list_route and #detail_route for your viewset. But be careful with your DRF version. Because those decorators merged together as #action in DRF 3.8+. Here is the announcement.
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
class BusinessViewSet(viewsets.ModelViewSet):
queryset = Business.objects.all()
serializer_class = BusinessSerializer
#action(detail=True, methods=["GET"], url_path="invoices")
def invoices(self, request, pk=None):
"""
Your codes comes here to return related result.
pk variable contains the param value from url.
if you do not specify the url_path properties then action will accept the function's name as url path.
"""
entity = Invoice.objects.filter(business=pk)
serializer = self.get_serializer(entity, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Then, you will be able to call this endpoints from;
http://example.com/api/business/{{PK}}/invoices/
http://example.com/api/business/1/invoices/
http://example.com/api/business/3/invoices/
http://example.com/api/business/23/invoices/
Here you can find more details about #actions from documentation.
PS: Don't forget to control empty entity results in your codes. You should return correct response with correct status codes.
im trying to add a custom action to my ViewSet in Django2, using django-rest-framework. Problem is that my serializer is not serializing nested model and thus giving me error:
{
"labels": [
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got Label."
]
},
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got Label."
]
}
]
}
I have two models which have M:N relationship.
Label model:
class Label(models.Model):
name = models.CharField(max_length=30, help_text='Name of Label')
desc = models.CharField(max_length=200, help_text='Description of Label')
def __str__(self):
return self.name
LabelSet model:
class LabelSet(models.Model):
labels = models.ManyToManyField(Label, blank=True, help_text='ManyToMany field of corresponding labels')
name = models.CharField(max_length=30, help_text='Name of Label Set')
desc = models.CharField(max_length=200, help_text='Description of Label Set')
def __str__(self):
return self.name
Machine Model:
class Machine(models.Model):
name = models.CharField(max_length=30, help_text='Name of machine')
desc = models.CharField(max_length=200, help_text='Description of machine')
location = models.ForeignKey(Location, null=True, blank=True, on_delete=models.CASCADE, help_text='ID of machine location')
labelset = models.ForeignKey(LabelSet, null=True, blank=True, on_delete=models.DO_NOTHING, help_text='ID of set of labels relevant for this machine')
def __str__(self):
return self.name
Serializers:
class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = '__all__'
class LabelSetSerializer(serializers.ModelSerializer):
qs = Label.objects.all().values()
labels = LabelSerializer(qs, many=True)
class Meta:
depth = 1
model = LabelSet
fields = ('name', 'desc', 'labels')
Custom action in viewsets.py (I want to retrieve available labels by machine, so path is /machines/{id}/labels
class MachineViewSet(viewsets.ModelViewSet):
'''
A viewset used for retrieving and editing Machine instances.
'''
#permission_classes = (DRYPermissions,)
serializer_class = MachineSerializer
queryset = Machine.objects.all()
# /api/v1/machines/{machine_id}/labels
#action(detail=True)
def labels(self, request, pk=None):
# Get labelset id
ls = Machine.objects.get(pk=pk).labelset
# Get LabelSet instance
serializer = LabelSetSerializer(data=model_to_dict(ls))
if serializer.is_valid():
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The endpoint works fine, but when querying /machines/1/labels i got the response which is the first snippet:
"Invalid data. Expected a dictionary, but got Label."
Im literally out of ideas, tried even making dict from qs = Label.objects.all().values() in Serializer, no luck.
Thanks to #Jerin Peter George, output is now:
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"name": "TestSet",
"desc": "asd",
"labels": [
{
"id": 1,
"name": "OK",
"desc": "desc"
},
{
"id": 2,
"name": "Broken",
"desc": "asd"
}
]
}
So /api/v1/machines/1/labels works, but suddenly /api/v1/machines/ does not. (502 Bad Gateway with error TypeError: 'LabelSet' object is not iterable)
APP level urls:
from django.conf.urls import url
from devices.viewsets import *
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'devices', DeviceViewSet, base_name='device')
router.register(r'projects', ProjectViewSet, base_name='project')
router.register(r'locations', LocationViewSet, base_name='location')
router.register(r'industries', IndustryViewSet, base_name='industry')
router.register(r'companies', CompanyViewSet, base_name='companies')
router.register(r'project_types', ProjectTypeViewSet, base_name='project_type')
router.register(r'device_types', DeviceTypeViewSet, base_name='device_type')
router.register(r'machines', MachineViewSet, base_name='machine')
router.register(r'records', RecordViewSet, base_name='record')
router.register(r'labels', LabelViewSet, base_name='label')
router.register(r'labelsets', LabelSetViewSet, base_name='label_set')
urlpatterns = router.urls
App level urls.py
from django.contrib import admin
from django.conf.urls import url
from django.urls import include, path
from rest_framework.documentation import include_docs_urls
from rest_framework_expiring_authtoken import views
from devices.views import AudioUploadView
API_PREFIX = 'api/v1/'
urlpatterns = [
url(API_PREFIX + 'docs/', include_docs_urls(title='API Docs')),
url(API_PREFIX + 'admin/', admin.site.urls),
url(API_PREFIX + 'api-token-auth/', views.obtain_expiring_auth_token),
path(API_PREFIX, include('devices.urls'))
]
EDIT: SOLVED
Apparently i added one more nested serializer to MachineSerializer
class MachineSerializer(serializers.ModelSerializer):
labelsets = LabelSetSerializer(many=True)
class Meta:
model = Machine
fields = '__all__'
So removing the line labelsets = LabelSetSerializer(many=True) did the trick.
And that is where the error came from, now is everything working as expected, thanks:)
Replace your labels() with below snippet,
#action(detail=True)
def labels(self, request, pk=None):
# Get labelset id
ls = Machine.objects.get(pk=pk).labelset
# Get LabelSet instance
serializer = LabelSetSerializer(ls)
return Response(serializer.data)
I try to send the following data to my django application:
{
"hashtags": ["hashtag"],
"title": "title",
"message": "message"
}
and i get this response:
{
"hashtags": [
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
]
}
I have the following view defined in views.py
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = IsAuthorizedOwnerOrReadOnly,
the models are defined like this:
class Post(models.Model):
ambassador = models.OneToOneField("User")
publish_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
hashtags = models.ManyToManyField("PostHashtags", related_query_name="posts")
title = models.CharField(max_length=TEXT_SHORT, null=True, blank=True)
message = models.CharField(max_length=TEXT_MIDDLE, null=True, blank=True)
class PostHashtags(models.Model):
hashtag = models.CharField(max_length=TEXT_SHORT, null=False)
def __unicode__(self):
return self.hashtag
and i define the serializers like this:
class PostHashtagSerializer(serializers.ModelSerializer):
class Meta:
model = PostHashtags
fields = ("hashtag",)
class PostSerializer(serializers.ModelSerializer):
hashtags = PostHashtagSerializer(many=True)
class Meta:
model = Post
fields = ("id", "hashtags", "title", "message",)
read_only_fields = ("id", 'account_name',)
It seems like the hashtags are not created automatically using my current serialisation config. Is there a way to have my hashtags created if they do not yet exist, and if they do exist, have the Post use that same hashtag? In that case, how should my serialisers be defined?
EDIT 1:
After GwynBleidD's suggestions I now get the following error:
The `.create()` method does not support writable nestedfields by default.
Write an explicit `.create()` method for serializer PostSerializer , or set `read_only=True` on nested serializer fields.
Does anyone have a suggestion for such a create method?
EDIT 2: solved it using the following serialisers
class PostHashtagSerializer(serializers.ModelSerializer):
hashtag = serializers.CharField()
class Meta:
model = PostHashtags
fields = ("hashtag",)
class PostSerializer(serializers.ModelSerializer):
hashtags = PostHashtagSerializer(many=True,)
class Meta:
model = Post
fields = ("ambassador", "hashtags", "title", "message",)
def create(self, validated_data):
hashtags_data = validated_data.pop('hashtags')
post = Post.objects.create(**validated_data)
for hashtag in hashtags_data:
ht = PostHashtags.objects.create()
ht.hashtag = hashtag.get("hashtag")
ht.save()
post.hashtags.add(ht)
post.save()
return post
Hashtags are not string, but dict in that example. You have to submit:
{
"hashtags": [{"hashtag": "hashtag"}],
"title": "title",
"message": "message"
}
when I go to http://127.0.0.1:8000/movies/
it get error : djangorestframework :type object has no attribute 'id'
Why can't I get the id value?
Please help me,thank you!
Here is my code
serialize.py
class MovieSerializer(serializers.ModelSerializer):
link = serializers.HyperlinkedRelatedField(source='id', view_name='movie_detail')
class Meta:
model = Movie
fields = ( 'link', 'title')
class MovieDetail(object):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
urls.py
urlpatterns = patterns('',
url(r'^movies/$', MovieList.as_view(), name='movie_list'),
url(r'^movies/(?P<pk>[0-9]+)$', MovieDetail.as_view(), name='movie_detail'),
how can I edit it to show the result like this:
"results": [
{
"link": "http://127.0.0.1:8000/movies/1",
"title": "test",
},
{
"link": "http://127.0.0.1:8000/movies/2",
"title": "test",
},
]
A HyperlinkedRelatedField must have a source that points at a related object on the model. You are looking for a HyperlinkedIdentityField, which will give a link for the current object, similar to what a HyperlinkedModelSerializer will do automatically for you.
class MovieSerializer(serializers.ModelSerializer):
link = serializers.HyperlinkedIdentityField(view_name='movie_detail')
class Meta:
model = Movie
fields = ( 'link', 'title', )