Package: django-custom-table
django-custom-tableOverviewDjango Custom Table is a framework for building the back end to a no-code platform in Django or for adding no-code customizible tables to a Django application.InstallationInstall usingpippipinstalldjango-custom-tableor clone the project from githubgitclonehttps://github.com/warrenwestfall/django-custom-tableAddcustom_tableto yourINSTALLED_APPSsetting.INSTALLED_APPS=[...'custom_table',]Example UsageFirst create a metadata model. This model will store all of the customizations users make to customizable tables. The model stores the app label and model name of the custom table model being customized, so you can have only one metadata model for mutlple customizable table models.Edit or create an app'smodels.pymodulefromcustom_table.modelsimportMetadatafromcustom_table.formatsimportDefaultFormatclassMyMetadata(Metadata):created=models.DateTimeField('Created',auto_now_add=True,db_index=True)modified=models.DateTimeField('Modified',auto_now=True)classMeta:ct_format_class=DefaultFormat()Your metadata model will need to inherit fromcustom_table.models.Metadataand provide a format class (as thect_format_classmeta attribute) that tells Django Custom Table how customazaton metadata will be stored and how to format the metadata for use in form and list renderng. How to build your own format class is decribribed later. This example adds some additional audit trail fields.Next create one or more customzable models.Create a new customizable model or edit and existing one to make it customizablefromdjango.dbimportmodelsfromcustom_table.modelsimportCustomizableMeta,CustomizableMixinSTORAGE_FIELDS={"indexed_char":{"num_to_create":10,"field_class":models.CharField,"field_class_params":{"max_length":128,"db_index":True,},},"char":{"num_to_create":30,"field_class":models.CharField,"field_class_params":{"max_length":128,},},"indexed_integer":{"num_to_create":20,"field_class":models.IntegerField,"field_class_params":{"db_index":True,},},"integer":{"num_to_create":20,"field_class":models.IntegerField,"field_class_params":{},},"text":{"num_to_create":30,"field_class":models.TextField,"field_class_params":{},},"float":{"num_to_create":10,"field_class":models.FloatField,"field_class_params":{},},"boolean":{"num_to_create":10,"field_class":models.BooleanField,"field_class_params":{},},"datetime":{"num_to_create":10,"field_class":models.DateTimeField,"field_class_params":{},},"decimal-1000-2":{"num_to_create":10,"field_class":models.DecimalField,"field_class_params":{"max_digits":1000,"decimal_places":2,},},}classMyCustomTable(models.Model,CustomizableMixin,metaclass=CustomizableMeta):created=models.DateTimeField('Created',auto_now_add=True,db_index=True)modified=models.DateTimeField('Modified',auto_now=True)example_static_field=models.CharField('Example Static Field',max_length=32)classMeta:ct_metadata_model=MyMetadatact_storage_fields=STORAGE_FIELDSct_db_field_prefix='ctf_'Setcustom_table.models.CustomizableMetaas the metaclass and provide act_storage_fieldsmeta attribute, a dictionary describing how to generate storage fields and how to map your users custom fields to storage fields. Also it is best to providect_db_field_prefixwith a string to prefix to all storage field names and prevent conflicts with ant static fields in your model.If you require your own form and/or list metadata formats you can create your own metadata format classfromcustom_table.formatsimportBaseFormatclassRestSpaFormat(BaseFormat):""" Format metadata for use in a Single Page App with a REST back endand a Javascript front end.Form metadate follows react-jsonschema-form"""defget_custom_fields(self,metadata):""" Must return a list of dictionaries containing field_name and field_type[{"name": "example_field_name","type": "char"},]Overide this in order to store the metadata in the custom_data fieldin a different format than what is expected by Metadata.save().This is used by the metadata model to calculate storage mappingsIn this example we will assume that our apllication stores the metadatain the expected format above."""returnmetadata.custom_datadefget_list_metadata(self,metadata):""" Should return the metadata required for a Django or front end viewto render a grid or list.Overide this to produce output required by your appicationIf it is desired that the list view be entirely dynamic that methodshould combine both static Django fields and custom fields.In this example we include static Django fields and convert them todictionary/json objects. We also add some simplication of the typeto combine types that are stored differently but rendered the same."""all_fields=self.get_all_fields(metadata)# print(all_fields)list_metadata=[]forfieldinall_fields:type=field['type']iftype.startswith('indexed_'):type=type[8:]list_field={'name':field['name'],'type':type,}if'list'infield:forlist_property,valueinfield['list'].items():list_field[list_property]=valuelist_metadata.append(list_field)returnlist_metadatadefget_form_metadata(self,metadata):""" Should return the metadata required for a Django or front end viewto render a form.Overide this to produce output required by your appicationIf it is desired that the form view be entirely dynamic that methodshould combine both static Django fields and custom fields.In this example we include static Django fields andoutput from metadata in a format that should work withreact-jsonschema-form"""all_fields=self.get_all_fields(metadata)form_metadata={'title':metadata.title,'type':'object','properties':{},}forfieldinall_fields:properties={'type':field['type'],}iffield['type']in('indexed_char','char','text',):properties['type']='string'iffield['typethon']in('indexed_integer','integer',):properties['type']='integer'iffield['type']=='float':properties['type']='number'iffield['type']=='datetime':properties['type']='string'properties['format']='date-time'iffield['type'].startswith('decimal'):# _, max_digits, decimal_places = field['type'].split('_')properties['type']='string'properties['format']=field['type']if'form'infield:forform_property,valueinfield['form'].items():properties[snake_to_camel(form_property)]=valueform_metadata['properties'][field['name']]=propertiesreturnform_metadataThe above example is taken from the built in django-custom-table formats and can be used as is as followsfromcustom_table.formatsimportRestSpaFormatWhen creating views, you can use the Django Custom table base view classes that have helper methods for reading and writing using customizable models.This how a view for a rest web service might look.In aviews.pyclassRestMetadataListView(BaseMetadataView):metadata_model=RestSpaFormatMetadatainclude_metadata=Truedefget(self,request):returnJsonResponse(self.get_list(),safe=False)defpost(self,request):new_record=self.create(json.loads(request.body))returnJsonResponse({'pk':new_record.pk},status=201)classRestMetadataDetailView(BaseMetadataView):metadata_model=RestSpaFormatMetadatainclude_metadata=Truealways_update_fields=['modified']defget(self,request,name_or_pk):returnJsonResponse(self.get_detail(name_or_pk),safe=False)defpatch(self,request,name_or_pk):self.update_fields(name_or_pk,json.loads(request.body))returnHttpResponse(status=202)defdelete(self,request,name_or_pk):self.delete_record(name_or_pk)returnHttpResponse(status=204)In aurls.pyrest_urlpatterns=[path('metadata/',RestMetadataListView.as_view()),path('metadata/<str:name_or_pk>/',RestMetadataDetailView.as_view()),path('data/<str:name>/',RestCustomTableListView.as_view(),path('data/<str:name>/<int:pk>/',RestCustomTableDetailView.as_view(),]html_urlpatterns=[path('<str:name>/',HtmlCustomTableListView.as_view()),path('<str:name>/<int:pk>/',HtmlCustomTableDetailView.as_view()),]urlpatterns=[path('admin/',admin.site.urls),path('rest/',include(rest_urlpatterns)),path('html/',include(html_urlpatterns)),]metadata_modeltells the view what metadata model to use to discover and render all customizationsinclude_metadatais used to tell the view to always include metadata in calls toget_list()andget_detail()always_update_fieldstells the view what additional fields tell Django to update in thesave()method called inupdate_fields()