mirror of https://github.com/jumpserver/jumpserver
perf: 支持全局的 labels (#12043)
* perf: 支持全局的 labels * perf: stash * stash * stash * stash * stash * perf: 优化 labels * stash * perf: add debug sql * perf: 修改 labels * perf: 优化提交 * perf: 优化提交 labels * perf: 基本完成 * perf: 完成 labels 搜索 * perf: 优化 labels * perf: 去掉不用 debug --------- Co-authored-by: ibuler <ibuler@qq.com>pull/12253/head
parent
a91cb1afd5
commit
8291a81efd
@ -1,43 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from ..models import Label
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['LabelViewSet']
|
||||
|
||||
|
||||
class LabelViewSet(OrgBulkModelViewSet):
|
||||
model = Label
|
||||
filterset_fields = ("name", "value")
|
||||
search_fields = filterset_fields
|
||||
serializer_class = serializers.LabelSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
if request.query_params.get("distinct"):
|
||||
self.serializer_class = serializers.LabelDistinctSerializer
|
||||
self.queryset = self.queryset.values("name").distinct()
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = Label.objects.prefetch_related(
|
||||
'assets').annotate(asset_count=Count("assets"))
|
||||
return self.queryset
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.10 on 2023-11-22 07:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0125_auto_20231011_1053'),
|
||||
('labels', '0002_auto_20231103_1659'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
),
|
||||
]
|
@ -1,47 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.db.models import Count
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import Label
|
||||
|
||||
|
||||
class LabelSerializer(BulkOrgResourceModelSerializer):
|
||||
asset_count = serializers.ReadOnlyField(label=_("Assets amount"))
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields_mini = ['id', 'name']
|
||||
fields_small = fields_mini + [
|
||||
'value', 'category', 'is_active',
|
||||
'date_created', 'comment',
|
||||
]
|
||||
fields_m2m = ['asset_count', 'assets']
|
||||
fields = fields_small + fields_m2m
|
||||
read_only_fields = (
|
||||
'category', 'date_created', 'asset_count',
|
||||
)
|
||||
extra_kwargs = {
|
||||
'assets': {'required': False, 'label': _('Asset')}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
queryset = queryset.prefetch_related('assets') \
|
||||
.annotate(asset_count=Count('assets'))
|
||||
return queryset
|
||||
|
||||
|
||||
class LabelDistinctSerializer(BulkOrgResourceModelSerializer):
|
||||
value = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = ("name", "value")
|
||||
|
||||
@staticmethod
|
||||
def get_value(obj):
|
||||
labels = Label.objects.filter(name=obj["name"])
|
||||
return ', '.join([label.value for label in labels])
|
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,141 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.api.generic import JMSModelViewSet
|
||||
from common.utils import is_true
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from orgs.utils import current_org
|
||||
from rbac.models import ContentType
|
||||
from rbac.serializers import ContentTypeSerializer
|
||||
from . import serializers
|
||||
from .models import Label, LabeledResource
|
||||
|
||||
__all__ = ['LabelViewSet']
|
||||
|
||||
|
||||
class ContentTypeViewSet(JMSModelViewSet):
|
||||
serializer_class = ContentTypeSerializer
|
||||
http_method_names = ['get', 'head', 'options']
|
||||
rbac_perms = {
|
||||
'default': 'labels.view_contenttype',
|
||||
'resources': 'labels.view_contenttype',
|
||||
}
|
||||
page_default_limit = None
|
||||
can_labeled_content_type = []
|
||||
model = ContentType
|
||||
|
||||
@classmethod
|
||||
def get_can_labeled_content_type_ids(cls):
|
||||
if cls.can_labeled_content_type:
|
||||
return cls.can_labeled_content_type
|
||||
content_types = ContentType.objects.all()
|
||||
for ct in content_types:
|
||||
model_cls = ct.model_class()
|
||||
if not model_cls:
|
||||
continue
|
||||
if model_cls._meta.parents:
|
||||
continue
|
||||
if 'labels' in model_cls._meta._forward_fields_map.keys():
|
||||
# if issubclass(model_cls, LabeledMixin):
|
||||
cls.can_labeled_content_type.append(ct.id)
|
||||
return cls.can_labeled_content_type
|
||||
|
||||
def get_queryset(self):
|
||||
ids = self.get_can_labeled_content_type_ids()
|
||||
queryset = ContentType.objects.filter(id__in=ids)
|
||||
return queryset
|
||||
|
||||
@action(methods=['GET'], detail=True, serializer_class=serializers.ContentTypeResourceSerializer)
|
||||
def resources(self, request, *args, **kwargs):
|
||||
self.page_default_limit = 100
|
||||
content_type = self.get_object()
|
||||
model = content_type.model_class()
|
||||
|
||||
if issubclass(model, OrgModelMixin):
|
||||
queryset = model.objects.filter(org_id=current_org.id)
|
||||
else:
|
||||
queryset = model.objects.all()
|
||||
|
||||
keyword = request.query_params.get('search')
|
||||
if keyword:
|
||||
queryset = content_type.filter_queryset(queryset, keyword)
|
||||
return self.get_paginated_response_from_queryset(queryset)
|
||||
|
||||
|
||||
class LabelContentTypeResourceViewSet(JMSModelViewSet):
|
||||
serializer_class = serializers.ContentTypeResourceSerializer
|
||||
rbac_perms = {
|
||||
'default': 'labels.view_labeledresource',
|
||||
'update': 'labels.change_labeledresource',
|
||||
}
|
||||
ordering_fields = ('res_type', 'date_created')
|
||||
|
||||
def get_queryset(self):
|
||||
label_pk = self.kwargs.get('label')
|
||||
res_type = self.kwargs.get('res_type')
|
||||
label = get_object_or_404(Label, pk=label_pk)
|
||||
content_type = get_object_or_404(ContentType, id=res_type)
|
||||
bound = self.request.query_params.get('bound', '1')
|
||||
res_ids = LabeledResource.objects.filter(res_type=content_type, label=label) \
|
||||
.values_list('res_id', flat=True)
|
||||
res_ids = set(res_ids)
|
||||
model = content_type.model_class()
|
||||
if is_true(bound):
|
||||
queryset = model.objects.filter(id__in=list(res_ids))
|
||||
else:
|
||||
queryset = model.objects.exclude(id__in=list(res_ids))
|
||||
keyword = self.request.query_params.get('search')
|
||||
if keyword:
|
||||
queryset = content_type.filter_queryset(queryset, keyword)
|
||||
return queryset
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
label_pk = self.kwargs.get('label')
|
||||
res_type = self.kwargs.get('res_type')
|
||||
content_type = get_object_or_404(ContentType, id=res_type)
|
||||
label = get_object_or_404(Label, pk=label_pk)
|
||||
res_ids = request.data.get('res_ids', [])
|
||||
|
||||
LabeledResource.objects \
|
||||
.filter(res_type=content_type, label=label) \
|
||||
.exclude(res_id__in=res_ids).delete()
|
||||
resources = []
|
||||
for res_id in res_ids:
|
||||
resources.append(LabeledResource(res_type=content_type, res_id=res_id, label=label, org_id=current_org.id))
|
||||
LabeledResource.objects.bulk_create(resources, ignore_conflicts=True)
|
||||
return Response({"total": len(res_ids)})
|
||||
|
||||
|
||||
class LabelViewSet(OrgBulkModelViewSet):
|
||||
model = Label
|
||||
filterset_fields = ("name", "value")
|
||||
search_fields = filterset_fields
|
||||
serializer_classes = {
|
||||
'default': serializers.LabelSerializer,
|
||||
'resource_types': ContentTypeSerializer,
|
||||
}
|
||||
rbac_perms = {
|
||||
'resource_types': 'labels.view_label',
|
||||
'keys': 'labels.view_label',
|
||||
}
|
||||
|
||||
@action(methods=['GET'], detail=False)
|
||||
def keys(self, request, *args, **kwargs):
|
||||
queryset = Label.objects.all()
|
||||
keyword = request.query_params.get('search')
|
||||
if keyword:
|
||||
queryset = queryset.filter(name__icontains=keyword)
|
||||
keys = queryset.values_list('name', flat=True).distinct()
|
||||
return Response(keys)
|
||||
|
||||
|
||||
class LabeledResourceViewSet(OrgBulkModelViewSet):
|
||||
model = LabeledResource
|
||||
filterset_fields = ("label__name", "label__value", "res_type", "res_id", "label")
|
||||
search_fields = filterset_fields
|
||||
serializer_classes = {
|
||||
'default': serializers.LabeledResourceSerializer,
|
||||
}
|
||||
ordering_fields = ('res_type', 'date_created')
|
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class LabelsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'labels'
|
@ -0,0 +1,54 @@
|
||||
# Generated by Django 4.1.10 on 2023-11-06 10:38
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Label',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('internal', models.BooleanField(default=False, verbose_name='Internal')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('name', models.CharField(db_index=True, max_length=64, verbose_name='Name')),
|
||||
('value', models.CharField(max_length=64, verbose_name='Value')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Label',
|
||||
'unique_together': {('name', 'value', 'org_id')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LabeledResource',
|
||||
fields=[
|
||||
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
|
||||
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('res_id', models.CharField(db_index=True, max_length=36, verbose_name='Resource ID')),
|
||||
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='labels.label')),
|
||||
('res_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
@ -0,0 +1,52 @@
|
||||
# Generated by Django 4.1.10 on 2023-11-03 08:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_assets_labels(apps, schema_editor):
|
||||
old_label_model = apps.get_model('assets', 'Label')
|
||||
new_label_model = apps.get_model('labels', 'Label')
|
||||
asset_model = apps.get_model('assets', 'Asset')
|
||||
labeled_item_model = apps.get_model('labels', 'LabeledResource')
|
||||
|
||||
old_labels = old_label_model.objects.all()
|
||||
new_labels = []
|
||||
old_new_label_map = {}
|
||||
for label in old_labels:
|
||||
new_label = new_label_model(name=label.name, value=label.value, org_id=label.org_id)
|
||||
old_new_label_map[label.id] = new_label
|
||||
new_labels.append(new_label)
|
||||
new_label_model.objects.bulk_create(new_labels, ignore_conflicts=True)
|
||||
|
||||
label_relations = asset_model.labels.through.objects.all()
|
||||
bulk_size = 1000
|
||||
count = 0
|
||||
content_type = apps.get_model('contenttypes', 'contenttype').objects.get_for_model(asset_model)
|
||||
|
||||
while True:
|
||||
relations = label_relations[count:count + bulk_size]
|
||||
if not relations:
|
||||
break
|
||||
count += bulk_size
|
||||
|
||||
tagged_items = []
|
||||
for relation in relations:
|
||||
new_label = old_new_label_map[relation.label_id]
|
||||
tagged_item = labeled_item_model(
|
||||
label_id=new_label.id, res_type=content_type,
|
||||
res_id=relation.asset_id, org_id=new_label.org_id
|
||||
)
|
||||
tagged_items.append(tagged_item)
|
||||
labeled_item_model.objects.bulk_create(tagged_items, ignore_conflicts=True)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('labels', '0001_initial'),
|
||||
('assets', '0125_auto_20231011_1053')
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_assets_labels),
|
||||
]
|
@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.1.10 on 2023-11-15 10:58
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('labels', '0002_auto_20231103_1659'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='labeledresource',
|
||||
options={'verbose_name': 'Labeled resource'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='labeledresource',
|
||||
name='label',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='labeled_resources', to='labels.label'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='labeledresource',
|
||||
unique_together={('label', 'res_type', 'res_id', 'org_id')},
|
||||
),
|
||||
]
|
@ -0,0 +1,13 @@
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.db import models
|
||||
|
||||
from .models import LabeledResource
|
||||
|
||||
__all__ = ['LabeledMixin']
|
||||
|
||||
|
||||
class LabeledMixin(models.Model):
|
||||
labels = GenericRelation(LabeledResource, object_id_field='res_id', content_type_field='res_type')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
@ -0,0 +1,38 @@
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import JMSOrgBaseModel
|
||||
|
||||
|
||||
class Label(JMSOrgBaseModel):
|
||||
name = models.CharField(max_length=64, verbose_name=_("Name"), db_index=True)
|
||||
value = models.CharField(max_length=64, unique=False, verbose_name=_("Value"))
|
||||
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
||||
|
||||
class Meta:
|
||||
unique_together = [('name', 'value', 'org_id')]
|
||||
verbose_name = _('Label')
|
||||
|
||||
@lazyproperty
|
||||
def res_count(self):
|
||||
return self.labeled_resources.count()
|
||||
|
||||
def __str__(self):
|
||||
return '{}:{}'.format(self.name, self.value)
|
||||
|
||||
|
||||
class LabeledResource(JMSOrgBaseModel):
|
||||
label = models.ForeignKey(Label, on_delete=models.CASCADE, related_name='labeled_resources')
|
||||
res_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
res_id = models.CharField(max_length=36, verbose_name=_("Resource ID"), db_index=True)
|
||||
resource = GenericForeignKey('res_type', 'res_id')
|
||||
|
||||
class Meta:
|
||||
unique_together = [('label', 'res_type', 'res_id', 'org_id')]
|
||||
verbose_name = _('Labeled resource')
|
||||
|
||||
def __str__(self):
|
||||
return '{} => {}'.format(self.label, self.resource)
|
@ -0,0 +1,54 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Count
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers.fields import ObjectRelatedField
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from .models import Label, LabeledResource
|
||||
|
||||
__all__ = ['LabelSerializer', 'LabeledResourceSerializer', 'ContentTypeResourceSerializer']
|
||||
|
||||
|
||||
class LabelSerializer(BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = Label
|
||||
fields = ['id', 'name', 'value', 'res_count', 'date_created', 'date_updated']
|
||||
read_only_fields = ('date_created', 'date_updated', 'res_count')
|
||||
extra_kwargs = {
|
||||
'res_count': {'label': _('Resource count')},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.annotate(res_count=Count('labeled_resources'))
|
||||
return queryset
|
||||
|
||||
|
||||
class LabeledResourceSerializer(serializers.ModelSerializer):
|
||||
res_type = ObjectRelatedField(
|
||||
queryset=ContentType.objects, attrs=('app_label', 'model', 'name'), label=_("Resource type")
|
||||
)
|
||||
label = ObjectRelatedField(queryset=Label.objects, attrs=('name', 'value'))
|
||||
resource = serializers.CharField(label=_("Resource"))
|
||||
|
||||
class Meta:
|
||||
model = LabeledResource
|
||||
fields = ('id', 'label', 'res_type', 'res_id', 'date_created', 'resource', 'date_updated')
|
||||
read_only_fields = ('date_created', 'date_updated', 'resource')
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.select_related('label', 'res_type')
|
||||
return queryset
|
||||
|
||||
|
||||
class ContentTypeResourceSerializer(serializers.Serializer):
|
||||
id = serializers.CharField()
|
||||
name = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def get_name(obj):
|
||||
return str(obj)
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from . import api
|
||||
|
||||
app_name = 'labels'
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'labels', api.LabelViewSet, 'label')
|
||||
router.register(r'labels/(?P<label>.*)/resource-types/(?P<res_type>.*)/resources',
|
||||
api.LabelContentTypeResourceViewSet, 'label-content-type-resource')
|
||||
router.register(r'labeled-resources', api.LabeledResourceViewSet, 'labeled-resource')
|
||||
router.register(r'resource-types', api.ContentTypeViewSet, 'content-type')
|
||||
|
||||
urlpatterns = [
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:185acf00dbd3e3ef37e9faf300c438e120f257fc876e72135cecb1234dd5e453
|
||||
size 166353
|
||||
oid sha256:790917753a2bc455aaa6a74322b18f7cbdb7ba860f4c08c46263e7762fb3fbe7
|
||||
size 165198
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e4af35b685fe0eb73e4dd9790669164bb823065d994a24f5441c9cff7a6e99e7
|
||||
size 135811
|
||||
oid sha256:a46c7aff5f314cbab9849e4c11671f81b849f98fad73887ced5b2012635b8a97
|
||||
size 135290
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
from rest_framework import viewsets
|
||||
|
||||
from .. import serializers
|
||||
from ..models import ContentType
|
||||
|
||||
|
||||
class ContentTypeViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = serializers.ContentTypeSerializer
|
||||
filterset_fields = ("app_label", "model",)
|
||||
search_fields = filterset_fields
|
||||
queryset = ContentType.objects.all()
|
@ -0,0 +1,13 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import ContentType
|
||||
|
||||
__all__ = ['ContentTypeSerializer']
|
||||
|
||||
|
||||
class ContentTypeSerializer(serializers.ModelSerializer):
|
||||
app_display = serializers.CharField()
|
||||
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = ('id', 'app_label', 'app_display', 'model', 'name')
|
Loading…
Reference in new issue