-
Notifications
You must be signed in to change notification settings - Fork 44
Description
Problem
Each project requires some Admin page that has specific tools to only project admins/operators.
Up until now we were building separate pages for each project with same base operations which causes repetitive tasks - doing same thing over and over again.
Requirements
- Same look, feel on every project.
- Following aspects should be develop in a way that it should be very easy to implement on the project side:
- Listing records: A Table with the support of pagination, sorting, filtering, searching
- Actions column on tables: If a list requires some operations, it should have an actions column
- Record info page
- Record edit page
Solution
Thought process
Feel free to modify this. I am throwing what comes to my mind freely.
My goal is to achieve ability to create new tools without needing to write any React app in a fast pace.
This feature will be only usable by admins and operators so there is no need to worry about being UI perfect.
Entry point view
- URL Path must be
r'/admintools(/.*?)?'and it must be last item inurlpatterns. - Should be single.
- Guarded with
@company_employee_requireddecorator - Template should not extend from project
base.html. Must act like a separate thing. - Template should load React application which will operate as SPA (Single Page Application)
API endpoints:
- URL Path must be
r'/admintools/api/...' - A config end point for React app should be exists:
r'/admintools/api/app_config'
React App should automatically generate pages, navigation menu items from config and incoming JSON data for specific pages.
Idea for ADMIN_TOOL_LINKS
@dataclass
class AdminToolLinkGroup:
name: str
icon: str
links: List[AdminToolLink]
def add_link(self, link: AdminToolLink):
link.group = self
self.links.append(link)
def json_encode(self):
# Standard converting to dict operation
@dataclass
class AdminToolLink:
name: str
group: Optional[AdminToolLinkGroup]
icon: str
view_type: str # Type of page. Table list, info page or edit/create form
api_url: str # Most pages requires a API endpoint to interract
@property
def link(self):
# An util function encode string to URL safe string
url = f'{self.group.name}/{self.name}' if self.group else self.name
value = f'{ADMIN_TOOL_ROOT_URL}/{url}'
return value
def json_encode(self):
# standart converting to dict operationAnother Idea for admintools wrap url function of Django when adding new API end points:
admintools_api_url will add extra params to a global variable and will return url() function of Django as a result.
app_config view will use this global variable to build paths and send to page.
urlpatterns = [
admintools_api_url(
r'^some_url$',
views.SomeAPI.as_view(),
name='some_url',
icon='fas fa-user',
group_name='Users',
page_type='list', # Instead of this, the page type can be sent with `OPTIONS` HTTP method?
# Whatever needed for React side
),
]Idea for app_config endpoint:
@company_employee_required
def api_config(request):
# Or maybe make it generatable from API endpoints?
from admintools.constants.links import ADMIN_TOOLS_LINKS
paths = generate_paths(ADMIN_TOOLS_LINKS)
response = json_response_okay({
'paths': paths
# Other needed information like current user info
# a boolean variable to determine to show django-admin link
})
return responseIn the React root component: config will be fetched, paths will be used for:
- Generating routes for
react-router - Generating dashboard links
- Generating navigation links
- Generating sidebar links for groups
Idea for listing records:
Every decision should be made in API endpoint. React should generate the table using variables provided from API JSON response
# Sample JSON response that should be generated from API
response = json_response_okay({
'header_columns': [
{
'slug': 'id',
'name': 'ID',
'sortable': true,
'filterable': true,
},
{
'slug': 'created_at',
'name': 'Registration Date',
'sortable': true,
'filterable': true,
'cast': 'date', # will use `new Date(date).toLocaleDateString()` in JS side
},
# ...
],
'item_actions': [
{
'icon': 'fas fa-pencil',
'operation': 'link',
'url': '/admintools/users/{id}',
'url_replace': 'id', # can be omitted or set to `False` if not wanted
},
{
'icon': 'fas fa-trash',
'operation': 'delete', # This will generate a link with a confirm modal
'url': '/admintools/api/users/{id}',
'url_replace': 'id' # can be omitted or set to `False` if not wanted
},
],
'data': [],
'total_count': 0,
'page': 1,
'page_size': 1,
'total_pages': 1,
})Idea for record editing:
Validation schema should also be provided from API end point:
# When a form page opens, React will request information on form using 'OPTIONS' HTTP method
# Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
# OPTIONS response:
response = json_response_okay({
'fields': [
{
'name': 'username',
'label': 'Username',
'field': 'text-input',
validation: [] # This should be something that we can convert to `zod` validation schema.
},
],
})Then the end point GET method should provide the values if its an edit page. If it is a create page either React should not make a API call or API endpoint should return an empty JSON.
API Endpoint Structure
Each API endpoint must have OPTIONS HTTP method defined. When React app gets table-list as type it should make another request to same endpoint with GET HTTP method to fetch the data.
OPTIONS call can be cached and can be re-used as long as React App keeps opened.
class SomeAPI(AdminToolsBaseAPI):
def options(self, request):
# This method should be inside `AdminToolsBaseAPI` it can be overridden if necessary
response = json_response_okay({
'type': 'table-list', # Other options are: info, upsert (a general term used to do update or insert operations)
# Some other options needed to build the page...
})
return responseTools to use for React
- MUI because it looks nice, easy. Developing components with it also easy.
- react-router for building routes
- zod validation NPM package. Works very good with TypeScript
- conform to integrate
zodschemas to React forms. - TypeScript for better error catching