Introduction
Clear and interactive documentation is essential in API development. It ensures that developers can easily understand and utilize your API, reducing confusion and speeding up the integration process. One of the best tools for achieving this is Swagger UI, which provides an interactive interface for your API documentation based on the OpenAPI Specification (OAS). In this guide, we will explore how to integrate Swagger UI with a Django Blog API project, making your API documentation user-friendly and accessible.
Why Use Swagger UI?
Swagger UI is a powerful tool that automatically generates interactive API documentation from an OpenAPI specification. It allows developers to:
- Explore API endpoints through a visual interface.
- Test API requests directly from the browser.
- Understand request and response structures without needing to sift through the code.
While other documentation tools like ReDoc offer various features, Swagger UI stands out for its interactive design and ease of implementation. This makes it a valuable tool for helping developers understand and test your API directly from the documentation.
Project Setup: Our Blog API
We will create a simple Blog API using Django REST Framework to demonstrate the integration of Swagger UI. Our API will allow users to:
- Create, read, update, and delete blog posts.
- Add and manage comments on blog posts.
- Tag and categorize blog posts.
Prerequisites
Before we begin, ensure you have the following:
- Python 3.8 or higher installed.
- Basic familiarity with Django and Django REST Framework.
- A code editor (e.g., VS Code, PyCharm).
Step 1. Create a New Django Project
Let’s start by setting up a new Django project.
# Create a virtual environmentpython -m venv blogapi_envsource blogapi_env/bin/activate # On Windows: blogapi_env\Scripts\activate# Install Django and DRFpip install django djangorestframework# Start a new Django projectdjango-admin startproject blogapicd blogapi# Create the blog apppython manage.py startapp blog# Create a virtual environment python -m venv blogapi_env source blogapi_env/bin/activate # On Windows: blogapi_env\Scripts\activate # Install Django and DRF pip install django djangorestframework # Start a new Django project django-admin startproject blogapi cd blogapi # Create the blog app python manage.py startapp blog# Create a virtual environment python -m venv blogapi_env source blogapi_env/bin/activate # On Windows: blogapi_env\Scripts\activate # Install Django and DRF pip install django djangorestframework # Start a new Django project django-admin startproject blogapi cd blogapi # Create the blog app python manage.py startapp blog
Enter fullscreen mode Exit fullscreen mode
Step 2. Define Models
Next, let’s define the models for our blog in blog/models.py.
from django.db import modelsfrom django.contrib.auth.models import Userclass Category(models.Model):name = models.CharField(max_length=100)def __str__(self):return self.nameclass Post(models.Model):title = models.CharField(max_length=200)content = models.TextField()author = models.ForeignKey(User, on_delete=models.CASCADE)created_at = models.DateTimeField(auto_now_add=True)updated_at = models.DateTimeField(auto_now=True)categories = models.ManyToManyField(Category, related_name='posts')def __str__(self):return self.titleclass Comment(models.Model):post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')author = models.ForeignKey(User, on_delete=models.CASCADE)content = models.TextField()created_at = models.DateTimeField(auto_now_add=True)def __str__(self):return f"Comment by {self.author.username} on {self.post.title}"from django.db import models from django.contrib.auth.models import User class Category(models.Model): name = models.CharField(max_length=100) def __str__(self): return self.name class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) categories = models.ManyToManyField(Category, related_name='posts') def __str__(self): return self.title class Comment(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') author = models.ForeignKey(User, on_delete=models.CASCADE) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Comment by {self.author.username} on {self.post.title}"from django.db import models from django.contrib.auth.models import User class Category(models.Model): name = models.CharField(max_length=100) def __str__(self): return self.name class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) categories = models.ManyToManyField(Category, related_name='posts') def __str__(self): return self.title class Comment(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') author = models.ForeignKey(User, on_delete=models.CASCADE) content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Comment by {self.author.username} on {self.post.title}"
Enter fullscreen mode Exit fullscreen mode
Step 3. Create Serializers
Now, let’s create serializers in blog/serializers.py to convert our models into JSON format.
from rest_framework import serializersfrom .models import Category, Post, Commentfrom django.contrib.auth.models import Userclass UserSerializer(serializers.ModelSerializer):class Meta:model = Userfields = ('id', 'username', 'email')class CategorySerializer(serializers.ModelSerializer):class Meta:model = Categoryfields = ('id', 'name')class CommentSerializer(serializers.ModelSerializer):author = UserSerializer(read_only=True)class Meta:model = Commentfields = ('id', 'post', 'author', 'content', 'created_at')read_only_fields = ('post',)class PostSerializer(serializers.ModelSerializer):author = UserSerializer(read_only=True)comments = CommentSerializer(many=True, read_only=True)categories = CategorySerializer(many=True, read_only=True)class Meta:model = Postfields = ('id', 'title', 'content', 'author', 'created_at', 'updated_at', 'categories', 'comments')from rest_framework import serializers from .models import Category, Post, Comment from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ('id', 'username', 'email') class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('id', 'name') class CommentSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) class Meta: model = Comment fields = ('id', 'post', 'author', 'content', 'created_at') read_only_fields = ('post',) class PostSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) comments = CommentSerializer(many=True, read_only=True) categories = CategorySerializer(many=True, read_only=True) class Meta: model = Post fields = ('id', 'title', 'content', 'author', 'created_at', 'updated_at', 'categories', 'comments')from rest_framework import serializers from .models import Category, Post, Comment from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ('id', 'username', 'email') class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('id', 'name') class CommentSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) class Meta: model = Comment fields = ('id', 'post', 'author', 'content', 'created_at') read_only_fields = ('post',) class PostSerializer(serializers.ModelSerializer): author = UserSerializer(read_only=True) comments = CommentSerializer(many=True, read_only=True) categories = CategorySerializer(many=True, read_only=True) class Meta: model = Post fields = ('id', 'title', 'content', 'author', 'created_at', 'updated_at', 'categories', 'comments')
Enter fullscreen mode Exit fullscreen mode
Step 4. Create Views
Let’s create our views using DRF’s ViewSets in blog/views.py.
from rest_framework import viewsetsfrom .models import Category, Post, Commentfrom .serializers import CategorySerializer, PostSerializer, CommentSerializerfrom rest_framework.permissions import IsAuthenticatedOrReadOnlyclass CategoryViewSet(viewsets.ModelViewSet):queryset = Category.objects.all()serializer_class = CategorySerializerclass PostViewSet(viewsets.ModelViewSet):queryset = Post.objects.all()serializer_class = PostSerializerpermission_classes = [IsAuthenticatedOrReadOnly]def perform_create(self, serializer):serializer.save(author=self.request.user)class CommentViewSet(viewsets.ModelViewSet):queryset = Comment.objects.all()serializer_class = CommentSerializerpermission_classes = [IsAuthenticatedOrReadOnly]def perform_create(self, serializer):post_id = self.kwargs.get('post_pk')post = Post.objects.get(pk=post_id)serializer.save(author=self.request.user, post=post)from rest_framework import viewsets from .models import Category, Post, Comment from .serializers import CategorySerializer, PostSerializer, CommentSerializer from rest_framework.permissions import IsAuthenticatedOrReadOnly class CategoryViewSet(viewsets.ModelViewSet): queryset = Category.objects.all() serializer_class = CategorySerializer class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): serializer.save(author=self.request.user) class CommentViewSet(viewsets.ModelViewSet): queryset = Comment.objects.all() serializer_class = CommentSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): post_id = self.kwargs.get('post_pk') post = Post.objects.get(pk=post_id) serializer.save(author=self.request.user, post=post)from rest_framework import viewsets from .models import Category, Post, Comment from .serializers import CategorySerializer, PostSerializer, CommentSerializer from rest_framework.permissions import IsAuthenticatedOrReadOnly class CategoryViewSet(viewsets.ModelViewSet): queryset = Category.objects.all() serializer_class = CategorySerializer class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): serializer.save(author=self.request.user) class CommentViewSet(viewsets.ModelViewSet): queryset = Comment.objects.all() serializer_class = CommentSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): post_id = self.kwargs.get('post_pk') post = Post.objects.get(pk=post_id) serializer.save(author=self.request.user, post=post)
Enter fullscreen mode Exit fullscreen mode
Step 5. Configure URLs
Create URL patterns in blog/urls.py.
from django.urls import path, includefrom rest_framework.routers import DefaultRouterfrom .views import CategoryViewSet, PostViewSet, CommentViewSetrouter = DefaultRouter()router.register(r'categories', CategoryViewSet)router.register(r'posts', PostViewSet)router.register(r'comments', CommentViewSet)urlpatterns = [path('', include(router.urls)),]from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import CategoryViewSet, PostViewSet, CommentViewSet router = DefaultRouter() router.register(r'categories', CategoryViewSet) router.register(r'posts', PostViewSet) router.register(r'comments', CommentViewSet) urlpatterns = [ path('', include(router.urls)), ]from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import CategoryViewSet, PostViewSet, CommentViewSet router = DefaultRouter() router.register(r'categories', CategoryViewSet) router.register(r'posts', PostViewSet) router.register(r'comments', CommentViewSet) urlpatterns = [ path('', include(router.urls)), ]
Enter fullscreen mode Exit fullscreen mode
Update the main blogapi/urls.py.
from django.contrib import adminfrom django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('api/', include('blog.urls')),]from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('blog.urls')), ]from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('blog.urls')), ]
Enter fullscreen mode Exit fullscreen mode
Step 6. Run Migrations and Start the Server
Run the following commands to apply migrations and start the development server.
# Create and apply migrationspython manage.py makemigrationspython manage.py migrate# Start the serverpython manage.py runserver# Create and apply migrations python manage.py makemigrations python manage.py migrate # Start the server python manage.py runserver# Create and apply migrations python manage.py makemigrations python manage.py migrate # Start the server python manage.py runserver
Enter fullscreen mode Exit fullscreen mode
Visit http://127.0.0.1:8000/api/ to see your API endpoints.
Integrating Swagger UI with Our Blog API
With our basic Blog API established, it’s time to integrate Swagger UI to offer interactive documentation.
Step 1: Install Required Packages
Install the necessary packages for integrating Swagger UI.
pip install drf-spectacular drf-spectacular-sidecarpip install drf-spectacular drf-spectacular-sidecarpip install drf-spectacular drf-spectacular-sidecar
Enter fullscreen mode Exit fullscreen mode
- drf-spectacular: Generates OpenAPI schemas for your DRF API.
- drf-spectacular-sidecar: Serves static files for Swagger UI, making it easier to use with CSP or other security measures.
Step 2. Configure Django Settings
Update your blogapi/settings.py file to include drf-spectacular.
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','rest_framework','blog','drf_spectacular', # Add this line]# Configure REST Framework settingsREST_FRAMEWORK = {'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema','DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticatedOrReadOnly',],}# Configure Spectacular settingsSPECTACULAR_SETTINGS = {'TITLE': 'Blog API','DESCRIPTION': 'API for managing blog posts, comments, and categories','VERSION': '1.0.0','SERVE_INCLUDE_SCHEMA': False,}INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'blog', 'drf_spectacular', # Add this line ] # Configure REST Framework settings REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ], } # Configure Spectacular settings SPECTACULAR_SETTINGS = { 'TITLE': 'Blog API', 'DESCRIPTION': 'API for managing blog posts, comments, and categories', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, }INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'blog', 'drf_spectacular', # Add this line ] # Configure REST Framework settings REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ], } # Configure Spectacular settings SPECTACULAR_SETTINGS = { 'TITLE': 'Blog API', 'DESCRIPTION': 'API for managing blog posts, comments, and categories', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, }
Enter fullscreen mode Exit fullscreen mode
Step 3. Add Swagger UI Endpoints
Update your blogapi/urls.py to include the Swagger UI endpoints:
from django.contrib import adminfrom django.urls import path, includefrom drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerViewurlpatterns = [path('admin/', admin.site.urls),path('api/', include('blog.urls')),# Swagger UI endpointspath('api/schema/', SpectacularAPIView.as_view(), name='schema'),path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),]from django.contrib import admin from django.urls import path, include from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('blog.urls')), # Swagger UI endpoints path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), ]from django.contrib import admin from django.urls import path, include from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView urlpatterns = [ path('admin/', admin.site.urls), path('api/', include('blog.urls')), # Swagger UI endpoints path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), ]
Enter fullscreen mode Exit fullscreen mode
Visit http://127.0.0.1:8000/api/docs/ to see the Swagger UI interface.
Step 4: Enhance API Documentation Using Decorators
To make our API documentation more informative, we will use decorators to add descriptions and response examples. Update blog/views.py.
from rest_framework import viewsetsfrom .models import Category, Post, Commentfrom .serializers import CategorySerializer, PostSerializer, CommentSerializerfrom rest_framework.permissions import IsAuthenticatedOrReadOnlyfrom drf_spectacular.utils import extend_schema, extend_schema_view@extend_schema_view(list=extend_schema(description='Get a list of all categories'),retrieve=extend_schema(description='Get details of a specific category'),create=extend_schema(description='Create a new category'),update=extend_schema(description='Update an existing category'),destroy=extend_schema(description='Delete a category'))class CategoryViewSet(viewsets.ModelViewSet):queryset = Category.objects.all()serializer_class = CategorySerializer@extend_schema_view(list=extend_schema(description='Get a list of all blog posts',responses={200: PostSerializer(many=True)}),retrieve=extend_schema(description='Get details of a specific blog post including comments',responses={200: PostSerializer}),create=extend_schema(description='Create a new blog post',responses={201: PostSerializer}),update=extend_schema(description='Update an existing blog post',responses={200: PostSerializer}),destroy=extend_schema(description='Delete a blog post',responses={204: None}))class PostViewSet(viewsets.ModelViewSet):queryset = Post.objects.all()serializer_class = PostSerializerpermission_classes = [IsAuthenticatedOrReadOnly]def perform_create(self, serializer):serializer.save(author=self.request.user)@extend_schema_view(list=extend_schema(description='Get a list of all comments'),retrieve=extend_schema(description='Get details of a specific comment'),create=extend_schema(description='Create a new comment on a post'),update=extend_schema(description='Update an existing comment'),destroy=extend_schema(description='Delete a comment'))class CommentViewSet(viewsets.ModelViewSet):queryset = Comment.objects.all()serializer_class = CommentSerializerpermission_classes = [IsAuthenticatedOrReadOnly]def perform_create(self, serializer):post_id = self.kwargs.get('post_pk')post = Post.objects.get(pk=post_id)serializer.save(author=self.request.user, post=post)from rest_framework import viewsets from .models import Category, Post, Comment from .serializers import CategorySerializer, PostSerializer, CommentSerializer from rest_framework.permissions import IsAuthenticatedOrReadOnly from drf_spectacular.utils import extend_schema, extend_schema_view @extend_schema_view( list=extend_schema(description='Get a list of all categories'), retrieve=extend_schema(description='Get details of a specific category'), create=extend_schema(description='Create a new category'), update=extend_schema(description='Update an existing category'), destroy=extend_schema(description='Delete a category') ) class CategoryViewSet(viewsets.ModelViewSet): queryset = Category.objects.all() serializer_class = CategorySerializer @extend_schema_view( list=extend_schema( description='Get a list of all blog posts', responses={200: PostSerializer(many=True)} ), retrieve=extend_schema( description='Get details of a specific blog post including comments', responses={200: PostSerializer} ), create=extend_schema( description='Create a new blog post', responses={201: PostSerializer} ), update=extend_schema( description='Update an existing blog post', responses={200: PostSerializer} ), destroy=extend_schema( description='Delete a blog post', responses={204: None} ) ) class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): serializer.save(author=self.request.user) @extend_schema_view( list=extend_schema(description='Get a list of all comments'), retrieve=extend_schema(description='Get details of a specific comment'), create=extend_schema(description='Create a new comment on a post'), update=extend_schema(description='Update an existing comment'), destroy=extend_schema(description='Delete a comment') ) class CommentViewSet(viewsets.ModelViewSet): queryset = Comment.objects.all() serializer_class = CommentSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): post_id = self.kwargs.get('post_pk') post = Post.objects.get(pk=post_id) serializer.save(author=self.request.user, post=post)from rest_framework import viewsets from .models import Category, Post, Comment from .serializers import CategorySerializer, PostSerializer, CommentSerializer from rest_framework.permissions import IsAuthenticatedOrReadOnly from drf_spectacular.utils import extend_schema, extend_schema_view @extend_schema_view( list=extend_schema(description='Get a list of all categories'), retrieve=extend_schema(description='Get details of a specific category'), create=extend_schema(description='Create a new category'), update=extend_schema(description='Update an existing category'), destroy=extend_schema(description='Delete a category') ) class CategoryViewSet(viewsets.ModelViewSet): queryset = Category.objects.all() serializer_class = CategorySerializer @extend_schema_view( list=extend_schema( description='Get a list of all blog posts', responses={200: PostSerializer(many=True)} ), retrieve=extend_schema( description='Get details of a specific blog post including comments', responses={200: PostSerializer} ), create=extend_schema( description='Create a new blog post', responses={201: PostSerializer} ), update=extend_schema( description='Update an existing blog post', responses={200: PostSerializer} ), destroy=extend_schema( description='Delete a blog post', responses={204: None} ) ) class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): serializer.save(author=self.request.user) @extend_schema_view( list=extend_schema(description='Get a list of all comments'), retrieve=extend_schema(description='Get details of a specific comment'), create=extend_schema(description='Create a new comment on a post'), update=extend_schema(description='Update an existing comment'), destroy=extend_schema(description='Delete a comment') ) class CommentViewSet(viewsets.ModelViewSet): queryset = Comment.objects.all() serializer_class = CommentSerializer permission_classes = [IsAuthenticatedOrReadOnly] def perform_create(self, serializer): post_id = self.kwargs.get('post_pk') post = Post.objects.get(pk=post_id) serializer.save(author=self.request.user, post=post)
Enter fullscreen mode Exit fullscreen mode
Testing Your Blog API with Swagger UI
Now that we have Swagger UI set up, let’s try out some API operations.
Step 1. Create a superuser
Create a superuser to authenticate in Swagger UI.
python manage.py createsuperuserpython manage.py createsuperuserpython manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode
Step 2 Test Endpoints
Use Swagger UI to test endpoints like creating categories, posts, and comments. For example:
Creating a New Category
1. In Swagger UI, expand the POST /api/categories/
endpoint
2. Click the “Try it out” button
3. Enter a sample category name in the request body:
{"name": "Technology"}{ "name": "Technology" }{ "name": "Technology" }
Enter fullscreen mode Exit fullscreen mode
4. Click “Execute”
5. You should see a successful response with status code 201 Created
Creating a Blog Post
1. In Swagger UI, expand the POST /api/posts/
endpoint
2. Click the “Try it out” button
3. Enter a sample post in the request body:
{"title": "Introduction to Swagger UI","content": "This is a post about integrating Swagger UI with Django REST Framework...","categories": [1]}{ "title": "Introduction to Swagger UI", "content": "This is a post about integrating Swagger UI with Django REST Framework...", "categories": [1] }{ "title": "Introduction to Swagger UI", "content": "This is a post about integrating Swagger UI with Django REST Framework...", "categories": [1] }
Enter fullscreen mode Exit fullscreen mode
4. Click “Execute”
5. You should see a successful response with status code 201 Created
Conclusion
Integrating Swagger UI with Django REST Framework for our Blog API project has significantly improved our API documentation. By following the steps in this guide, you’ve created an interactive, user-friendly interface that allows developers to explore and test your API endpoints directly from their browsers.
If you’d like to explore the full implementation, check out the complete code on GitHub. Feel free to clone the repository, experiment with the code, and use it as a starting point for your own projects.
Remember that good documentation is an ongoing process. As you add new features to your Blog API, make sure to update your schema descriptions and examples to keep your documentation comprehensive and current.
暂无评论内容