I have a listing system in my Django project, with a Tag object that attaches a list of Tags to a Listing. Each object has its own standard Viewset which is passed to router.register(). The current way I'm editing or creating Tags is through a POST or PATCH to /tags/. Ideally, I'd do this by doing a PATCH /listings/[id]/ with a JSON body of {"tags": [{"type": "foo", "text": "bar"}]}. I've provided a slightly simplified version of my code below.
serializers.py
class NestedTagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['text', 'type']
class ListingSerializer(serializers.ModelSerializer):
tags = NestedTagSerializer(many=True, read_only=False)
class Meta:
model = Listing
fields = ['tags', 'title', 'id']
models.py
class Listing(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
class Tag(models.Model):
listing = models.ForeignKey(Listing, on_delete=CASCADE)
text = models.CharField(max_length=255)
type = models.CharField(max_length=255)
Is there a nice way to do this with Django Rest Framework?
I faced similar issue before and for me routing was main issue.
To implement nested routing, can use drf-extensions
urls.py
from rest_framework import routers
from rest_framework_extensions.routers import NestedRouterMixin
class NestedDefaultRouter(NestedRouterMixin, routers.DefaultRouter):
pass
router = NestedDefaultRouter()
listing_router = router.register('listings/', ListingView)
tag_router = listing_router.register('tags', TagView, basename='tags', parents_query_lookups=['listing_id']
urlpartterns = router.urls
It will generate urls as following :
/listings/ ListingView listing-list
/listings/<parent_lookup_listing_id>/tags/ TagView listing-tags-list
/listings/<parent_lookup_listing_id>/tags/<pk>/ TagView listing-tags-detail
/listings/<parent_lookup_listing_id>/tags/<pk>\.<format>/ TagView listing-tags-detail
/listings/<parent_lookup_listing_id>/tags\.<format>/ TagView listing-tags-list
parent_lookup_listing_id will be used to determine listing
listing_id = self.kwargs.get('parent_lookup_listing_id')
Related
I'm actually create APIs on a Django Website using Django Rest Framework.
I'm trying to document them using Swagger.
I'm using Django 2.1, django-rest-swagger 2.2 and djangorestframework 3.11
Everything is nearly working as expected except something :
Let me explain you :
I have this model (models.py)
class Technology(models.Model):
"""
This model defines the different technologies
"""
name = models.CharField(max_length=CHAR_SHORT)
path = models.CharField(max_length=CHAR_SHORT, validators=[validate_tech_path], help_text='this is only used to construct the url')
image = models.ImageField()
mailer = models.EmailField(blank=True)
external = models.BooleanField(default=False)
internal = models.BooleanField(default=False)
class Meta:
verbose_name_plural = "technologies"
ordering = ['name']
def __str__(self):
return self.name
Then I have the corresponding serializer class (serializer.py):
class TechnologySerializer(serializers.ModelSerializer):
"""
This model defines the different technologies
"""
class Meta:
model = Technology
fields = ('id', 'name', 'path', 'image', 'mailer', 'external', 'internal')
Finally I have my view with generated APIs (views.py):
class TechnologyViewSet(viewsets.ModelViewSet):
queryset = Technology.objects.all()
serializer_class = TechnologySerializer
http_method_names = ['get','post','delete','put']
Here's the result :
Api description 1
Api description 2
As you see on the picture above, the parameters are une the json body.
Is it possible to have something like this for all the parameters :
API parameter wanted
Thanks a lot.
Try using ListCreateAPIView instead of modelviewset you will be able to see your post https://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview.
from rest_framework.generics import ListCreateAPIView
class TechnologyViewSet(ListCreateAPIView):
queryset = Technology.objects.all()
serializer_class = TechnologySerializer
Hope it help.
As I'm using django rest framework for building my product api.
Here is my model in models.py
class Tag(models.Model):
tag = models.CharField(max_length=10, unique=True)
class Product(models.Model):
product_name = models.CharField(max_length=100)
tag = models.ManyToManyField(Tag, blank=True, default=None, related_name='product_tag')
serializers.py :
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class ProductSerializer(serializers.HyperlinkedModelSerializer):
tag = TagSerializer(many=True, read_only=True)
class Meta:
model = Product
fields = '__all__'
views.py :
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
I have given the url for ProductViewset, so when I hit the api it gives me the results as well but it takes too much time to load, it takes around 2 minutes to give me the response.
I'm having 2000 product objects in database which needs to be populated.
When I exclude the 'tag' field in "ProductSerializer", response comes very fast with all 2000 records.
Please suggest where is the loophole, why its affecting performance so much especially when I add this ManyToMany field.
I always use django-debug-toolbar to debug my queryset to find bottleneck/duplicate query in my project. Django orm always using lazy load to retrieve related fields from database.
You can change this default behavior of your queryset by eager load your many to many field using prefetch_related.
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.prefetch_related('tag').all()
serializer_class = ProductSerializer
Reference: prefetch_related
I have two models,
class Publication(models.Model):
title = models.CharField(max_length=30)
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
I am trying to learn serializing using serpy.
I wrote two serializers, but I am not sure how to mention the model. I wrote a django rest framework serializer, as follows,
class PublicationSerializer(serializers.ModelSerializer):
class Meta:
model = Publication
fields = 'title',
class ArticleSerializer(serializers.ModelSerializer):
publications = PublicationSerializer(many=True)
class Meta:
model = Article
fields = '__all__'
This is the serializers that I wrote for using with Serpy.
class PublicationSerializer(serpy.Serializer):
title = serpy.Field()
class ArticleSerializer(serpy.Serializer):
headline = serpy.Field()
publications = PublicationSerializer()
I dont know where should I mention the model,
I would like to be able to serialize a queryset, say
Article.objects.all()
what changes must be made to use it with Django Rest Framework?
You apparently don't need to specify an associated model for the serpy serializer. Passing your Django objects to the appropriate serpy serializer class should suffice. Or not?
articles = Article.objects.all()
articles_serialized = ArticleSerializer(articles, many=True).data
I have a very simple app which at the moment declares two models: one is called "Content" and simply holds content data and the other is "Page" which includes "Content" as a OneToOneField.
The reason I've done this is so that I can have "Page" as an actual concrete class that I use and when other models in other modules I'm planning need page data, they can simply include "Content" as a OneToOneField. I've done it this way to avoid inheritance and use composition instead.
models.py:
from django.db import models
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
class Page(models.Model):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
content = models.OneToOneField(Content)
def __str__(self):
return self.content.title
admin.py:
from django.contrib import admin
from content.models import Page, Content
class ContentInline(admin.TabularInline):
model = Content
fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')
class PageAdmin(admin.ModelAdmin):
fields = ('slug',)
inlines = [ContentInline]
On the page admin I get this exception:
Exception at /admin/content/page/add/
<class 'content.models.Content'> has no ForeignKey to <class 'content.models.Page'>
What is says of course is correct, but I cannot seem to find a way of doing what I want, which is to include an inline of the non-defining side of a relationship. I don't want to declare the relationship on "Content" as then I'd have to define every single relationship to it inside that class which would introduce dependencies to other modules, which in my opinion it should know nothing about.
Using Django 1.6 on Python 3.3.
Edit: As indicated in the comments, I've decided to use inheritance. My initial concern about this was that I wanted the flexibility to be able to compose classes from multiple other classes. However, since the Django ORM does support multiple inheritance and if I'd realised that method was called "mixins" (new to Python) I would have got somewhere a lot sooner.
Example mixins with models:
from django.db import models
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
def __str__(self):
return self.title
class Meta:
abstract = True
class Data(models.Model):
data_name = models.CharField(max_length=200)
class Meta:
abstract = True
class Page(Content, Data):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
And then I can just use it as one model in admin.py.
Another solution is moving the OneToOneField from Content to Page
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
page = models.OneToOneField(Page, primary_key=True, related_name="content")
class Page(models.Model):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
def __str__(self):
return self.content.title
You can still do page.content and the inline form will work out of the box
EDIT:
One cons of that approach is that it will allow the user to create a page without assigning any content to it (in which case page.content will crash)
Its very easy to overcome this issue by creating custom form
class ContentAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
kwargs["empty_permitted"] = False
super(ContentAdminForm, self).__init__(*args, **kwargs)
Then in the admin page
class ContentInline(admin.TabularInline):
model = Content
form = ContentAdminForm
fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')
If you don't want to change your models at all, there's a django module to display the non-defining side inline: django_reverse_admin
You'll need to add django_reverse_admin to your requirements.txt:
-e git+https://github.com/anziem/django_reverse_admin.git#egg=django_reverse_admin
Then import it:
admin.py
from django.contrib import admin
from django_reverse_admin import ReverseModelAdmin
from content.models import Page, Content
# don't need to define an inline anymore for Content
class PageAdmin(ReverseModelAdmin):
fields = ('slug',)
inline_reverse = ['content']
inline_type = 'tabular' # or could be 'stacked'
I have a Django model that is hierarchical using django-mptt, which looks like:
class UOMCategory(MPTTModel, BaseModel):
"""
This represents categories of different unit of measurements.
"""
name = models.CharField(max_length=50, unique=True)
description = models.CharField(max_length=50, unique=True)
parent = TreeForeignKey('self', null=True, blank=True, related_name='%(app_label)s_%(class)s_sub_uom_categories')
The problem now is I created a REST API using Django REST Framework; how do I make sure that parent field returns serialized data?
Here is the Model Serializer:
class UOMCategorySerializer(BaseModelSerializer):
"""
REST API Serializer for UOMCategory model
"""
class Meta:
model = UOMCategory
In DRF you can use a serializer as a field in another serializer. However, recursion is not possible.
Tom Christie posted a solution on another question (Django rest framework nested self-referential objects). His solution will also work with your problem.
In your UOMCategorySerializer.Meta class you specify the fields you want to use, also list the parent and/or children field(s) there. Then you use Tom Christies solution.
In your case this would give:
class UOMCategorySerializer(ModelSerializer):
class Meta:
model = UOMCategory
fields = ('name', 'description', 'parent', 'children')
Tom Christies solution: By specifying what field to use for parent and/or children, you avoid using too much (and possibily endless) recursion:
UOMCategorySerializer.base_fields['parent'] = UOMCategorySerializer()
UOMCategorySerializer.base_fields['children'] = UOMCategorySerializer(many=True)
The above works for me in a similar situation.
Simple DRF API view using MPTT cache and DRF serializer.
from rest_framework import serializers
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from events.models import Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = (
"name",
"slug",
)
class CategoryTreeView(GenericAPIView):
serializer_class = CategorySerializer
def get(self, request, *args, **kwargs):
root_nodes = Category.objects.all().get_cached_trees()
data = []
for n in root_nodes:
data.append(self.recursive_node_to_dict(n))
return Response(data)
def recursive_node_to_dict(self, node):
result = self.get_serializer(instance=node).data
children = [self.recursive_node_to_dict(c) for c in node.get_children()]
if children:
result["children"] = children
return result