USAGE

Simple model view

# in your app models.py
from django.contrib.gis.db import models

class Feature(models.Model):
    geom = models.GeometryField(srid=4326)
    name = models.CharField(max_length=250)

# in your view file
from django.views.generic import ListView
from vectortiles.postgis.views import MVTView
from yourapp.models import Feature


class FeatureTileView(MVTView, ListView):
    model = Feature
    vector_tile_layer_name = "features"  # name for data layer in vector tile
    vector_tile_fields = ('name',)  # model fields or queryset annotates to include in tile
    # vector_tile_content_type = "application/x-protobuf"  # if you want to use custom content_type
    # vector_tile_queryset = None  # define a queryset for your features
    # vector_tile_queryset_limit = None  # as queryset could not be sliced, set here a limit for your features per tile
    # vector_tile_geom_name = "geom"  # geom field to consider in qs
    # vector_tile_extent = 4096  # tile extent
    # vector_tile_buffer = 256  # buffer around tile

# in your urls file
from django.urls import path
from yourapp import views


urlpatterns = [
    ...
    path('tiles/<int:z>/<int:x>/<int:y>', views.FeatureTileView.as_view(), name="feature-tile"),
    ...
]

Django Rest Framework

# in your views.py file
from vectortiles.rest_framework.renderers import MVTRenderer


class FeatureAPIView(BaseVectorTile, APIView):
    vector_tile_queryset = Feature.objects.all()
    vector_tile_layer_name = "features"
    vector_tile_fields = ('name', )
    vector_tile_queryset_limit = 100
    renderer_classes = (MVTRenderer, )

    def get(self, request, *args, **kwargs):
        return Response(self.get_tile(kwargs.get('x'), kwargs.get('y'), kwargs.get('z')))

# in your urls file
urlpatterns = [
    ...
    path('features/tiles/<int:z>/<int:x>/<int:y>', FeatureAPIView.as_view(),
         name="feature-tile-drf"),
    ...
]

# or extending viewset

class FeatureViewSet(BaseVectorTile, viewsets.ModelViewSet):
    queryset = Feature.objects.all()
    vector_tile_layer_name = "features"
    vector_tile_fields = ('name', )
    vector_tile_queryset_limit = 100

    @action(detail=False, methods=['get'], renderer_classes=(MVTRenderer, ),
            url_path='tiles/(?P<z>\d+)/(?P<x>\d+)/(?P<y>\d+)', url_name='tile')
    def tile(self, request, *args, **kwargs):
        return Response(self.get_tile(x=int(kwargs.get('x')), y=int(kwargs.get('y')), z=int(kwargs.get('z'))))

# in your urls file
router = SimpleRouter()
router.register(r'features', FeatureViewSet, basename='features')

urlpatterns += router.urls

then use http://your-domain/features/tiles/{z}/{x}/{y}.pbf

MapLibre Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>MapBox / MapLibre example</title>
    <style>
        html, body {
            margin: 0;
            padding: 0;
        }
    </style>
    <link href='https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.css' rel='stylesheet'/>
    {#    <link href='https://watergis.github.io/maplibre-gl-legend/maplibre-gl-legend.css' rel='stylesheet' />#}

</head>
<body>
<div id="map" style="width: 100%; height: 100vh"></div>
<script src='https://unpkg.com/maplibre-gl@2.4.0/dist/maplibre-gl.js'></script>
{#<script src="https://watergis.github.io/maplibre-gl-legend/maplibre-gl-legend.js"></script>#}

<script>
    var map = new maplibregl.Map({
        container: 'map',
        hash: true,
        style: 'https://demotiles.maplibre.org/style.json', // stylesheet location
        center: [1.77, 44.498], // starting position [lng, lat]
        zoom: 8 // starting zoom
    });
    var nav = new maplibregl.NavigationControl({visualizePitch: true});
    map.addControl(nav, 'top-right');
    var scale = new maplibregl.ScaleControl({
        maxWidth: 80,
        unit: 'metric'
    });
    map.addControl(scale);
    map.on('load', function () {
        map.addSource('layers', {
            'type': 'vector',
            'url': '{% url "layer-tilejson" %}'
        });
        map.addLayer(
            {
                'id': 'background2',
                'type': 'background',
                'paint': {
                    'background-color': '#F8F4F0',
                }

            }
        );

        map.addLayer(
            {
                'id': 'foret_publique',
                'type': 'fill',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'foret_publique',
                'paint': {
                    'fill-color': '#64b646',
                    'fill-opacity': 0.1
                }

            }
        );
        map.addLayer(
            {
                'id': 'parc_ou_reserve',
                'type': 'fill',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'parc_ou_reserve',
                'paint': {
                    'fill-color': '#64b646',
                    'fill-opacity': 0.1
                }

            }
        );

        map.addLayer(
            {
                'id': 'troncon_hydrographique',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_hydrographique',
                'paint': {
                    'line-color': '#A7D4E8',
                }

            }
        );
        map.addLayer(
            {
                'id': 'surface_hydrographique',
                'type': 'fill',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'surface_hydrographique',
                'paint': {
                    'fill-color': '#A7D4E8',
                    'fill-opacity': 1
                }

            }
        );
        map.addLayer(
            {
                'id': 'troncon_de_voie_ferree',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_de_voie_ferree',
                "filter": [
                    "all",
                    ["==", "$type", "LineString"],
                    ["!=", "nature", "Voie de service"],
                ],
                "paint": {
                    "line-color": "#bbb",
                    "line-width": {"base": 1.4, "stops": [[14, 0.4], [15, 0.75], [20, 2]]}
                }

            }
        );
        map.addLayer(
            {
                'id': 'troncon_de_voie_ferree_hatching',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_de_voie_ferree',
                "filter": [
                    "all",
                    ["==", "$type", "LineString"],
                    ["!=", "nature", "Voie de service"],
                ],
                "paint": {
                    "line-color": "#bbb",
                    "line-dasharray": [0.2, 8],
                    "line-width": {"base": 1.4, "stops": [[14.5, 0], [15, 3], [20, 8]]}
                }

            }
        );
        map.addLayer(
            {
                'id': 'troncon_de_voie_ferree_service',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_de_voie_ferree',
                "filter": [
                    "all",
                    ["==", "$type", "LineString"],
                    ["==", "nature", "Voie de service"],
                ],
                "paint": {
                    "line-color": "hsla(0, 0%, 73%, 0.77)",
                    "line-width": {"base": 1.4, "stops": [[14, 0.4], [20, 1]]}
                }

            }
        );
        map.addLayer(
            {
                'id': 'troncon_de_voie_ferree__service_hatching',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_de_voie_ferree',
                "filter": [
                    "all",
                    ["==", "$type", "LineString"],
                    ["==", "nature", "Voie de service"],
                ],
                "paint": {
                    "line-color": "hsla(0, 0%, 73%, 0.68)",
                    "line-dasharray": [0.2, 8],
                    "line-width": {"base": 1.4, "stops": [[14.5, 0], [15, 2], [20, 6]]}
                }

            }
        );
        map.addLayer(
            {
                'id': 'troncon_de_route',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_de_route',
                "filter": [
                    "all",
                    ["==", "$type", "LineString"],
                    ["!=", "nature", "Type autoroutier"],
                    ["!=", "nature", "Bretelle"],
                ],
                'layout': {
                    'line-cap': 'round',
                    'line-join': 'round'
                },
                'paint': {
                    'line-opacity': 1,
                    'line-color': ['match', ['get', 'nature'], // get the property
                        'Type autoroutier', '#FF0000',              // if 'GP' then yellow
                        'XX', 'black',               // if 'XX' then black
                        'white'],
                    'line-width': {"base": 1.2, "stops": [[13.5, 0], [14, 2.5], [20, 11.5]]}
                }
            }
        );
        map.addLayer(
            {
                'id': 'autoroute',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_de_route',
                "filter": [
                    "all",
                    ["==", "$type", "LineString"],
                    ["==", "nature", "Type autoroutier"],
                ],
                'layout': {
                    'line-cap': 'round',
                    'line-join': 'round'
                },
                'paint': {
                    'line-opacity': 1,
                    'line-color': '#fc8',
                    'line-width': {"base": 1.2, "stops": [[6.5, 0], [7, 0.5], [20, 18]]}
                }
            }
        );
        map.addLayer(
            {
                'id': 'autoroute_bretelles',
                'type': 'line',
                'source': 'layers',
                'source-layer': 'troncon_de_route',
                "filter": [
                    "all",
                    ["==", "$type", "LineString"],
                    ["==", "nature", "Bretelle"],
                ],
                "layout": {"line-cap": "round", "line-join": "round"},
                "paint": {
                    "line-color": "#fc8",
                    "line-width": {
                        "base": 1.2,
                        "stops": [[12.5, 0], [13, 1.5], [14, 2.5], [20, 11.5]]
                    }
                }
            }
        );
        map.addLayer(
            {
                'id': 'terrain_de_sport',
                'type': 'fill',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'terrain_de_sport',
                'paint': {
                    'fill-color': '#E0EDD8',
                    'fill-opacity': 1
                }

            }
        );
        map.addLayer(
            {
                'id': 'batiment',
                'type': 'fill',
                maxzoom: 16,
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'batiment',
                'paint': {
                    'fill-color': '#F1EAD8',
                    'fill-opacity': 0.8
                }

            }
        );
        map.addLayer(
            {
                'id': 'batiment-contours',
                'type': 'line',
                minzoom: 14,
                maxzoom: 16,
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'batiment',
                'layout': {
                    'line-cap': 'round',
                    'line-join': 'round'
                },
                'paint': {
                    'line-opacity': 0.8,
                    'line-color': '#D6C3AC',
                    'line-width': 2,
                }

            }
        );
        map.addLayer(
            {
                'id': 'batiment-3D',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                minzoom: 16,
                'source': 'layers',
                'source-layer': 'batiment',
                'type': 'fill-extrusion',
                'paint': {
                    'fill-extrusion-color': '#F1EAD8',

                    // Use an 'interpolate' expression to
                    // add a smooth transition effect to
                    // the buildings as the user zooms in.
                    'fill-extrusion-height': [
                        'interpolate',
                        ['linear'],
                        ['zoom'],
                        15,
                        0,
                        15.05,
                        ['get', 'hauteur']
                    ],
                    'fill-extrusion-base': [
                        'interpolate',
                        ['linear'],
                        ['zoom'],
                        15,
                        0,
                        15.05,
                        2.5
                    ],
                    'fill-extrusion-opacity': 0.6
                }

            }
        );
                map.addLayer(
            {
                'id': 'commune',
                'type': 'line',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'commune',
                'layout': {
                    'line-cap': 'round',
                    'line-join': 'round'
                },
                'paint': {
                    'line-opacity': 0.4,
                    'line-color': '#3636a8',
                    'line-width': 0.5,
                    'line-dasharray': [10, 10]
                }

            }
        );
        map.addLayer(
            {
                'id': 'departement',
                'type': 'line',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'departement',
                'layout': {
                    'line-cap': 'round',
                    'line-join': 'round'
                },
                'paint': {
                    'line-opacity': 0.8,
                    'line-color': '#479c47',
                    'line-width': 0.8
                }

            }
        );
        map.addLayer(
            {
                'id': 'region',
                'type': 'line',
                'filter': ['==', ['geometry-type'], 'Polygon'],
                'source': 'layers',
                'source-layer': 'region',
                'layout': {
                    'line-cap': 'round',
                    'line-join': 'round'
                },
                'paint': {
                    'line-opacity': 0.9,
                    'line-color': '#b12929',
                    'line-width': 1
                }

            }
        );
        map.addLayer(
            {
                "id": "commune_border",
                "type": "symbol",
                "source": "layers",
                "source-layer": "commune",
                "minzoom": 13,
                "layout": {
                    "symbol-placement": "line",
                    "symbol-spacing": 350,
                    "text-field": "{nom}",
                    "text-font": ["Noto Sans Italic"],
                    "text-letter-spacing": 0.2,
                    "text-max-width": 5,
                    "text-rotation-alignment": "map",
                    "text-size": 10
                },
                "paint": {
                    "text-color": "#3636a8",
                    "text-halo-color": "rgba(255,255,255,0.7)",
                    "text-halo-width": 1
                }
            }
        );
        map.addLayer(
            {
                "id": "commune_nom",
                "type": "symbol",
                "source": "layers",
                "source-layer": "commune_centre",
                "minzoom": 10,
                "maxzoom": 12,
                "layout": {
                    "symbol-placement": "point",
                    "symbol-spacing": 350,
                    "text-field": "{nom}",
                    "text-font": ["Noto Sans Italic"],
                    "text-letter-spacing": 0.2,
                    "text-max-width": 5,
                    "text-rotation-alignment": "map",
                    "text-size": 14
                },
                "paint": {
                    "text-color": "#3636a8",
                    "text-halo-color": "rgba(255,255,255,0.7)",
                    "text-halo-width": 1.5
                }
            }
        );
                map.addLayer(
            {
                "id": "eau_nom",
                "type": "symbol",
                "source": "layers",
                "source-layer": "troncon_hydrographique",
                "minzoom": 13,
                "layout": {
                    "symbol-placement": "line",
                    "symbol-spacing": 350,
                    "text-field": "{nom}",
                    "text-font": ["Noto Sans Italic"],
                    "text-letter-spacing": 0.2,
                    "text-max-width": 5,
                    "text-rotation-alignment": "map",
                    "text-size": 14
                },
                "paint": {
                    "text-color": "#74aee9",
                    "text-halo-color": "rgba(255,255,255,0.7)",
                    "text-halo-width": 1.5
                }
            }
        );
        // Create a popup, but don't add it to the map yet.
        var popup = new maplibregl.Popup({
            closeButton: false,
            closeOnClick: false
        });

        map.on('mouseenter', 'commune_nom', function (e) {
            // Change the cursor style as a UI indicator.
            map.getCanvas().style.cursor = 'pointer';
            console.log(e.features);
            var coordinates = e.features[0].geometry.coordinates.slice();
            var description = `${e.features[0].properties.nom} (${e.features[0].properties.population} hab.)`;

            // Ensure that if the map is zoomed out such that multiple
            // copies of the feature are visible, the popup appears
            // over the copy being pointed to.
            while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
                coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
            }

            // Populate the popup and set its coordinates
            // based on the feature found.
            popup.setLngLat(coordinates).setHTML(description).addTo(map);
        });

        map.on('mouseleave', 'city-centroid', function () {
            map.getCanvas().style.cursor = '';
            popup.remove();
        });
    }
);
</script>
</body>
</html>