adenh93 / django-typomatic

A simple solution for generating Typescript interfaces from your Django Rest Framework Serializers.
MIT License
126 stars 22 forks source link

Create a management command to generate the .d.ts file #11

Open ChickenF622 opened 3 years ago

ChickenF622 commented 3 years ago

I have created my own custom command, and figured it could be of use to other people it currently goes through each installed app checks for serializers and will automatically apply the @ts_interafce decorator to them:

import importlib
import inspect
import os
from types import ModuleType
from django.apps import AppConfig
from django.conf import settings
from django.core.management.base import BaseCommand
from django_typomatic import ts_interface, get_ts
from rest_framework.serializers import Serializer

class Command(BaseCommand):
    EXCLUDED_APPS = ['rest_framework']
    help = 'Creates a TypeScript definition file for all models registered in DRF serializers under src/ts/@types/django-models.d.ts'

    def create_model_mappings(self, app_config: AppConfig):
        """Updates the custom mappings with related models and their type in ts"""
        mappings = {}
        for model in app_config.get_models():
            for field in model._meta.get_fields():
                if field.related_model is not None:
                    ts_type = field.related_model.__name__
                    if field.many_to_many or field.one_to_many:
                        ts_type += '[]'
                    mappings[field.name] = ts_type
        return mappings

    def handle(self, *args, **options):
        from django.apps import apps
        for app_config in apps.get_app_configs():
            if app_config.name not in self.EXCLUDED_APPS:
                self.handle_app_config(app_config)
        # Remove Serializer from type name since it's redundant in TS
        ts = get_ts().replace('Serializer', '')
        type_file_location = os.path.join(settings.BASE_DIR, 'src/ts/@types/django-models.d.ts')
        with open(type_file_location, 'w') as type_file:
            type_file.write(ts)
        self.stdout.write(self.style.SUCCESS(f'Type file sucessfully generated at {type_file_location}'))

    def handle_app_config(self, app_config: AppConfig):
        try: #Check to see the app has serializers
            serializers_module: ModuleType = importlib.import_module(app_config.name + '.serializers')
        except ImportError:
            return
        mappings = self.create_model_mappings(app_config)
        serializers = inspect.getmembers(serializers_module, lambda member: self.is_serializer(member, serializers_module))
        for name, serializer in  serializers:
            # Get the class def and apply the ts_interface decorator to it
            base_class = getattr(serializers_module, name)
            ts_interface(mapping_overrides=mappings)(base_class)

    def is_serializer(self, member: object, module):
        """Checks to see if the given member is a serializer class and is a part of the given module"""
        return inspect.isclass(member) and issubclass(member, Serializer) and member.__module__ == module.__name__
adenh93 commented 3 years ago

Hi @ChickenF622,

Good stuff, this sounds useful! I'll check this out on the weekend 👍

Thank you