diff --git a/apps/charts/__init__.py b/apps/charts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/charts/admin.py b/apps/charts/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/charts/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/charts/apps.py b/apps/charts/apps.py new file mode 100644 index 0000000..ea2efb7 --- /dev/null +++ b/apps/charts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ChartsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.charts' diff --git a/apps/charts/migrations/__init__.py b/apps/charts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/charts/models.py b/apps/charts/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/apps/charts/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/charts/tests.py b/apps/charts/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/charts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/charts/urls.py b/apps/charts/urls.py new file mode 100644 index 0000000..b04ad52 --- /dev/null +++ b/apps/charts/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from apps.charts import views + +urlpatterns = [ + path("", views.index, name="charts"), +] \ No newline at end of file diff --git a/apps/charts/views.py b/apps/charts/views.py new file mode 100644 index 0000000..a524f0b --- /dev/null +++ b/apps/charts/views.py @@ -0,0 +1,13 @@ +from django.shortcuts import render +from django.core import serializers +from apps.pages.models import * + +# Create your views here. + +def index(request): + products = serializers.serialize('json', Product.objects.all()) + context = { + 'segment': 'charts', + 'products': products + } + return render(request, 'charts/index.html', context) diff --git a/apps/dyn_api/__init__.py b/apps/dyn_api/__init__.py new file mode 100644 index 0000000..6c7c5a7 --- /dev/null +++ b/apps/dyn_api/__init__.py @@ -0,0 +1,5 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + diff --git a/apps/dyn_api/admin.py b/apps/dyn_api/admin.py new file mode 100644 index 0000000..304ee83 --- /dev/null +++ b/apps/dyn_api/admin.py @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + +from django.contrib import admin + +# Register your models here. diff --git a/apps/dyn_api/apps.py b/apps/dyn_api/apps.py new file mode 100644 index 0000000..a96e878 --- /dev/null +++ b/apps/dyn_api/apps.py @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + +from django.apps import AppConfig + +class DynApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.dyn_api' diff --git a/apps/dyn_api/helpers.py b/apps/dyn_api/helpers.py new file mode 100644 index 0000000..cce2cb7 --- /dev/null +++ b/apps/dyn_api/helpers.py @@ -0,0 +1,63 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + +import datetime, sys, inspect, importlib + +from functools import wraps + +from django.db import models +from django.http import HttpResponseRedirect, HttpResponse + +from rest_framework import serializers + +class Utils: + @staticmethod + def get_class(config, name: str) -> models.Model: + return Utils.model_name_to_class(config[name]) + + @staticmethod + def get_manager(config, name: str) -> models.Manager: + return Utils.get_class(config, name).objects + + @staticmethod + def get_serializer(config, name: str): + class Serializer(serializers.ModelSerializer): + class Meta: + model = Utils.get_class(config, name) + fields = '__all__' + + return Serializer + + @staticmethod + def model_name_to_class(name: str): + + model_name = name.split('.')[-1] + model_import = name.replace('.'+model_name, '') + + module = importlib.import_module(model_import) + cls = getattr(module, model_name) + + return cls + +def check_permission(function): + @wraps(function) + def wrap(viewRequest, *args, **kwargs): + + try: + + # Check user + if viewRequest.request.user.is_authenticated: + + return function(viewRequest, *args, **kwargs) + + # For authentication for guests + return HttpResponseRedirect('/login/') + + except Exception as e: + + # On error + return HttpResponse( 'Error: ' + str( e ) ) + + return wrap diff --git a/apps/dyn_api/migrations/__init__.py b/apps/dyn_api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dyn_api/tests.py b/apps/dyn_api/tests.py new file mode 100644 index 0000000..c77ada2 --- /dev/null +++ b/apps/dyn_api/tests.py @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + +from django.test import TestCase + +# Create your tests here. diff --git a/apps/dyn_api/urls.py b/apps/dyn_api/urls.py new file mode 100644 index 0000000..e9e1cd8 --- /dev/null +++ b/apps/dyn_api/urls.py @@ -0,0 +1,16 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + +from django.contrib import admin +from django.urls import path +from apps.dyn_api import views + +urlpatterns = [ + path('api/', views.index, name="dynamic_api"), + + path('api//' , views.DynamicAPI.as_view(), name="model_api"), + path('api//' , views.DynamicAPI.as_view()), + path('api///' , views.DynamicAPI.as_view()), +] diff --git a/apps/dyn_api/views.py b/apps/dyn_api/views.py new file mode 100644 index 0000000..f77d080 --- /dev/null +++ b/apps/dyn_api/views.py @@ -0,0 +1,155 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + +from django.http import Http404 + +from django.contrib.auth.decorators import login_required +from django.utils.decorators import method_decorator +from django.shortcuts import render, redirect, get_object_or_404 + +from rest_framework.generics import get_object_or_404 +from rest_framework.views import APIView +from rest_framework.response import Response +from django.http import HttpResponse + +from django.conf import settings + +DYNAMIC_API = {} + +try: + DYNAMIC_API = getattr(settings, 'DYNAMIC_API') +except: + pass + +from .helpers import Utils + +def index(request): + + context = { + 'routes' : settings.DYNAMIC_API.keys(), + 'segment': 'dynamic_api' + } + + return render(request, 'dyn_api/index.html', context) + +class DynamicAPI(APIView): + + # READ : GET api/model/id or api/model + def get(self, request, **kwargs): + + model_id = kwargs.get('id', None) + try: + if model_id is not None: + + # Validate for integer + try: + model_id = int(model_id) + + if model_id < 0: + raise ValueError('Expect positive int') + + except ValueError as e: + return Response(data={ + 'message': 'Input Error = ' + str(e), + 'success': False + }, status=400) + + thing = get_object_or_404(Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')), id=model_id) + model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(instance=thing) + output = model_serializer.data + else: + all_things = Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')).all() + thing_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name')) + output = [] + for thing in all_things: + output.append(thing_serializer(instance=thing).data) + except KeyError: + return Response(data={ + 'message': 'this model is not activated or not exist.', + 'success': False + }, status=400) + except Http404: + return Response(data={ + 'message': 'object with given id not found.', + 'success': False + }, status=404) + return Response(data={ + 'data': output, + 'success': True + }, status=200) + + # CREATE : POST api/model/ + #@check_permission + def post(self, request, **kwargs): + try: + model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(data=request.data) + if model_serializer.is_valid(): + model_serializer.save() + else: + return Response(data={ + **model_serializer.errors, + 'success': False + }, status=400) + except KeyError: + return Response(data={ + 'message': 'this model is not activated or not exist.', + 'success': False + }, status=400) + return Response(data={ + 'message': 'Record Created.', + 'success': True + }, status=200) + + # UPDATE : PUT api/model/id/ + #@check_permission + def put(self, request, **kwargs): + try: + thing = get_object_or_404(Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')), id=kwargs.get('id')) + model_serializer = Utils.get_serializer(DYNAMIC_API, kwargs.get('model_name'))(instance=thing, + data=request.data, + partial=True) + if model_serializer.is_valid(): + model_serializer.save() + else: + return Response(data={ + **model_serializer.errors, + 'success': False + }, status=400) + except KeyError: + return Response(data={ + 'message': 'this model is not activated or not exist.', + 'success': False + }, status=400) + except Http404: + return Response(data={ + 'message': 'object with given id not found.', + 'success': False + }, status=404) + return Response(data={ + 'message': 'Record Updated.', + 'success': True + }, status=200) + + # DELETE : DELETE api/model/id/ + #@check_permission + def delete(self, request, **kwargs): + try: + model_manager = Utils.get_manager(DYNAMIC_API, kwargs.get('model_name')) + to_delete_id = kwargs.get('id') + model_manager.get(id=to_delete_id).delete() + except KeyError: + return Response(data={ + 'message': 'this model is not activated or not exist.', + 'success': False + }, status=400) + except Utils.get_class(DYNAMIC_API, kwargs.get('model_name')).DoesNotExist as e: + return Response(data={ + 'message': 'object with given id not found.', + 'success': False + }, status=404) + return Response(data={ + 'message': 'Record Deleted.', + 'success': True + }, status=200) diff --git a/apps/dyn_dt/.gitkeep b/apps/dyn_dt/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/dyn_dt/__init__.py b/apps/dyn_dt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dyn_dt/admin.py b/apps/dyn_dt/admin.py new file mode 100644 index 0000000..aeb9506 --- /dev/null +++ b/apps/dyn_dt/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from .models import * + +# Register your models here. + +admin.site.register(PageItems) +admin.site.register(HideShowFilter) +admin.site.register(ModelFilter) diff --git a/apps/dyn_dt/apps.py b/apps/dyn_dt/apps.py new file mode 100644 index 0000000..a92c5a5 --- /dev/null +++ b/apps/dyn_dt/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class DynDtConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.dyn_dt' diff --git a/apps/dyn_dt/forms.py b/apps/dyn_dt/forms.py new file mode 100644 index 0000000..d650f88 --- /dev/null +++ b/apps/dyn_dt/forms.py @@ -0,0 +1,3 @@ +from django import forms + +# Create your forms here. diff --git a/apps/dyn_dt/migrations/0001_initial.py b/apps/dyn_dt/migrations/0001_initial.py new file mode 100644 index 0000000..9aaeb35 --- /dev/null +++ b/apps/dyn_dt/migrations/0001_initial.py @@ -0,0 +1,63 @@ +# Generated by Django 4.2.9 on 2025-03-30 11:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="HideShowFilter", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("parent", models.CharField(blank=True, max_length=255, null=True)), + ("key", models.CharField(max_length=255)), + ("value", models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name="ModelFilter", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("parent", models.CharField(blank=True, max_length=255, null=True)), + ("key", models.CharField(max_length=255)), + ("value", models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name="PageItems", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("parent", models.CharField(blank=True, max_length=255, null=True)), + ("items_per_page", models.IntegerField(default=25)), + ], + ), + ] diff --git a/apps/dyn_dt/migrations/__init__.py b/apps/dyn_dt/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dyn_dt/models.py b/apps/dyn_dt/models.py new file mode 100644 index 0000000..6a12d09 --- /dev/null +++ b/apps/dyn_dt/models.py @@ -0,0 +1,24 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +# Create your models here. + +class PageItems(models.Model): + parent = models.CharField(max_length=255, null=True, blank=True) + items_per_page = models.IntegerField(default=25) + +class HideShowFilter(models.Model): + parent = models.CharField(max_length=255, null=True, blank=True) + key = models.CharField(max_length=255) + value = models.BooleanField(default=False) + + def __str__(self): + return self.key + +class ModelFilter(models.Model): + parent = models.CharField(max_length=255, null=True, blank=True) + key = models.CharField(max_length=255) + value = models.CharField(max_length=255) + + def __str__(self): + return self.key \ No newline at end of file diff --git a/apps/dyn_dt/templatetags/__init__.py b/apps/dyn_dt/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dyn_dt/templatetags/get_attribute.py b/apps/dyn_dt/templatetags/get_attribute.py new file mode 100644 index 0000000..9aab7fc --- /dev/null +++ b/apps/dyn_dt/templatetags/get_attribute.py @@ -0,0 +1,22 @@ +from django import template +from datetime import datetime + +register = template.Library() + + +@register.filter(name="getattribute") +def getattribute(value, arg): + try: + attr_value = getattr(value, arg) + + if isinstance(attr_value, datetime): + return attr_value.strftime("%Y-%m-%d %H:%M:%S") + + return attr_value + except: + return '' + + +@register.filter +def get(dict_data, key): + return dict_data.get(key, []) \ No newline at end of file diff --git a/apps/dyn_dt/tests.py b/apps/dyn_dt/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/dyn_dt/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/dyn_dt/urls.py b/apps/dyn_dt/urls.py new file mode 100644 index 0000000..c180ea5 --- /dev/null +++ b/apps/dyn_dt/urls.py @@ -0,0 +1,18 @@ +from django.urls import path +from apps.dyn_dt import views + +urlpatterns = [ + path('dynamic-dt/', views.index, name="dynamic_dt"), + + path('create-filter//', views.create_filter, name="create_filter"), + path('create-page-items//', views.create_page_items, name="create_page_items"), + path('create-hide-show-items//', views.create_hide_show_filter, name="create_hide_show_filter"), + path('delete-filter///', views.delete_filter, name="delete_filter"), + path('create//', views.create, name="create"), + path('delete///', views.delete, name="delete"), + path('update///', views.update, name="update"), + + path('export-csv//', views.ExportCSVView.as_view(), name='export_csv'), + + path('dynamic-dt//', views.model_dt, name="model_dt"), +] diff --git a/apps/dyn_dt/utils.py b/apps/dyn_dt/utils.py new file mode 100644 index 0000000..4042ea8 --- /dev/null +++ b/apps/dyn_dt/utils.py @@ -0,0 +1,13 @@ +from django.db.models import Q + +def user_filter(request, queryset, fields, fk_fields=[]): + value = request.GET.get('search') + + if value: + dynamic_q = Q() + for field in fields: + if field not in fk_fields: + dynamic_q |= Q(**{f'{field}__icontains': value}) + return queryset.filter(dynamic_q) + + return queryset \ No newline at end of file diff --git a/apps/dyn_dt/views.py b/apps/dyn_dt/views.py new file mode 100644 index 0000000..54b7775 --- /dev/null +++ b/apps/dyn_dt/views.py @@ -0,0 +1,315 @@ +import requests, base64, json, csv +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.utils import timezone +from django.http import HttpResponse, JsonResponse +from django.utils.safestring import mark_safe +from django.conf import settings +from django.urls import reverse +from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage +from django.urls import reverse +from django.views import View +from django.db import models +from pprint import pp + +from apps.dyn_dt.models import ModelFilter, PageItems, HideShowFilter +from apps.dyn_dt.utils import user_filter + +from cli import * + +# Create your views here. + +def index(request): + + context = { + 'routes' : settings.DYNAMIC_DATATB.keys(), + 'segment': 'dynamic_dt' + } + + return render(request, 'dyn_dt/index.html', context) + +def create_filter(request, model_name): + model_name = model_name.lower() + if request.method == "POST": + keys = request.POST.getlist('key') + values = request.POST.getlist('value') + for i in range(len(keys)): + key = keys[i] + value = values[i] + + ModelFilter.objects.update_or_create( + parent=model_name, + key=key, + defaults={'value': value} + ) + + return redirect(reverse('model_dt', args=[model_name])) + + +def create_page_items(request, model_name): + model_name = model_name.lower() + if request.method == 'POST': + items = request.POST.get('items') + page_items, created = PageItems.objects.update_or_create( + parent=model_name, + defaults={'items_per_page':items} + ) + return redirect(reverse('model_dt', args=[model_name])) + + +def create_hide_show_filter(request, model_name): + model_name = model_name.lower() + if request.method == "POST": + data_str = list(request.POST.keys())[0] + data = json.loads(data_str) + + HideShowFilter.objects.update_or_create( + parent=model_name, + key=data.get('key'), + defaults={'value': data.get('value')} + ) + + response_data = {'message': 'Model updated successfully'} + return JsonResponse(response_data) + + return JsonResponse({'error': 'Invalid request'}, status=400) + + +def delete_filter(request, model_name, id): + model_name = model_name.lower() + filter_instance = ModelFilter.objects.get(id=id, parent=model_name) + filter_instance.delete() + return redirect(reverse('model_dt', args=[model_name])) + + +def get_model_field_names(model, field_type): + """Returns a list of field names based on the given field type.""" + return [ + field.name for field in model._meta.get_fields() + if isinstance(field, field_type) + ] + +def model_dt(request, aPath): + aModelName = None + aModelClass = None + choices_dict = {} + + if aPath in settings.DYNAMIC_DATATB.keys(): + aModelName = settings.DYNAMIC_DATATB[aPath] + aModelClass = name_to_class(aModelName) + + if not aModelClass: + return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) + + #db_fields = [field.name for field in aModelClass._meta.get_fields() if not field.is_relation] + db_fields = [field.name for field in aModelClass._meta.fields] + fk_fields = get_model_fk_values(aModelClass) + db_filters = [] + for f in db_fields: + if f not in fk_fields.keys(): + db_filters.append( f ) + + for field in aModelClass._meta.fields: + if field.choices: + choices_dict[field.name] = field.choices + + field_names = [] + for field_name in db_fields: + fields, created = HideShowFilter.objects.get_or_create(key=field_name, parent=aPath.lower()) + if fields.key in db_fields: + field_names.append(fields) + + model_series = {} + for f in db_fields: + f_values = list ( aModelClass.objects.values_list( f, flat=True) ) + model_series[ f ] = ', '.join( str(i) for i in f_values) + + # model filter + filter_string = {} + filter_instance = ModelFilter.objects.filter(parent=aPath.lower()) + for filter_data in filter_instance: + if filter_data.key in db_fields: + filter_string[f'{filter_data.key}__icontains'] = filter_data.value + + order_by = request.GET.get('order_by', 'id') + if order_by not in db_fields: + order_by = 'id' + + queryset = aModelClass.objects.filter(**filter_string).order_by(order_by) + item_list = user_filter(request, queryset, db_fields, fk_fields.keys()) + + # pagination + page_items = PageItems.objects.filter(parent=aPath.lower()).last() + p_items = 25 + if page_items: + p_items = page_items.items_per_page + + page = request.GET.get('page', 1) + paginator = Paginator(item_list, p_items) + + try: + items = paginator.page(page) + except PageNotAnInteger: + return redirect(reverse('model_dt', args=[aPath])) + except EmptyPage: + return redirect(reverse('model_dt', args=[aPath])) + + read_only_fields = ('id', ) + + integer_fields = get_model_field_names(aModelClass, models.IntegerField) + date_time_fields = get_model_field_names(aModelClass, models.DateTimeField) + email_fields = get_model_field_names(aModelClass, models.EmailField) + text_fields = get_model_field_names(aModelClass, (models.TextField, models.CharField)) + + context = { + 'page_title': 'Dynamic DataTable - ' + aPath.lower().title(), + 'link': aPath, + 'field_names': field_names, + 'db_field_names': db_fields, + 'db_filters': db_filters, + 'items': items, + 'page_items': p_items, + 'filter_instance': filter_instance, + 'read_only_fields': read_only_fields, + + 'integer_fields': integer_fields, + 'date_time_fields': date_time_fields, + 'email_fields': email_fields, + 'text_fields': text_fields, + 'fk_fields_keys': list( fk_fields.keys() ), + 'fk_fields': fk_fields , + 'choices_dict': choices_dict, + 'segment': 'dynamic_dt' + } + return render(request, 'dyn_dt/model.html', context) + + +@login_required(login_url='/accounts/login/') +def create(request, aPath): + aModelClass = None + + if aPath in settings.DYNAMIC_DATATB.keys(): + aModelName = settings.DYNAMIC_DATATB[aPath] + aModelClass = name_to_class(aModelName) + + if not aModelClass: + return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) + + if request.method == 'POST': + data = {} + fk_fields = get_model_fk(aModelClass) + + for attribute, value in request.POST.items(): + if attribute == 'csrfmiddlewaretoken': + continue + + # Process FKs + if attribute in fk_fields.keys(): + value = name_to_class( fk_fields[attribute] ).objects.filter(id=value).first() + + data[attribute] = value if value else '' + + aModelClass.objects.create(**data) + + return redirect(request.META.get('HTTP_REFERER')) + + +@login_required(login_url='/accounts/login/') +def delete(request, aPath, id): + aModelClass = None + + if aPath in settings.DYNAMIC_DATATB.keys(): + aModelName = settings.DYNAMIC_DATATB[aPath] + aModelClass = name_to_class(aModelName) + + if not aModelClass: + return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) + + item = aModelClass.objects.get(id=id) + item.delete() + return redirect(request.META.get('HTTP_REFERER')) + + +@login_required(login_url='/accounts/login/') +def update(request, aPath, id): + aModelClass = None + + if aPath in settings.DYNAMIC_DATATB.keys(): + aModelName = settings.DYNAMIC_DATATB[aPath] + aModelClass = name_to_class(aModelName) + + if not aModelClass: + return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) + + item = aModelClass.objects.get(id=id) + fk_fields = get_model_fk(aModelClass) + + if request.method == 'POST': + for attribute, value in request.POST.items(): + + if attribute == 'csrfmiddlewaretoken': + continue + + if getattr(item, attribute, value) is not None: + + # Process FKs + if attribute in fk_fields.keys(): + value = name_to_class( fk_fields[attribute] ).objects.filter(id=value).first() + + setattr(item, attribute, value) + + item.save() + + return redirect(request.META.get('HTTP_REFERER')) + + + +# Export as CSV +class ExportCSVView(View): + def get(self, request, aPath): + aModelName = None + aModelClass = None + + if aPath in settings.DYNAMIC_DATATB.keys(): + aModelName = settings.DYNAMIC_DATATB[aPath] + aModelClass = name_to_class(aModelName) + + if not aModelClass: + return HttpResponse( ' > ERR: Getting ModelClass for path: ' + aPath ) + + db_field_names = [field.name for field in aModelClass._meta.get_fields()] + fields = [] + show_fields = HideShowFilter.objects.filter(value=False, parent=aPath.lower()) + + for field in show_fields: + if field.key in db_field_names: + fields.append(field.key) + else: + print(f"Field {field.key} does not exist in {aModelClass} model.") + + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = f'attachment; filename="{aPath.lower()}.csv"' + + writer = csv.writer(response) + writer.writerow(fields) # Write the header + + filter_string = {} + filter_instance = ModelFilter.objects.filter(parent=aPath.lower()) + for filter_data in filter_instance: + filter_string[f'{filter_data.key}__icontains'] = filter_data.value + + order_by = request.GET.get('order_by', 'id') + queryset = aModelClass.objects.filter(**filter_string).order_by(order_by) + + items = user_filter(request, queryset, db_field_names) + + for item in items: + row_data = [] + for field in fields: + try: + row_data.append(getattr(item, field)) + except AttributeError: + row_data.append('') + writer.writerow(row_data) + + return response \ No newline at end of file diff --git a/apps/pages/__init__.py b/apps/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pages/admin.py b/apps/pages/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/pages/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/pages/apps.py b/apps/pages/apps.py new file mode 100644 index 0000000..9b06e8d --- /dev/null +++ b/apps/pages/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PagesConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.pages" diff --git a/apps/pages/migrations/0001_initial.py b/apps/pages/migrations/0001_initial.py new file mode 100644 index 0000000..bf11793 --- /dev/null +++ b/apps/pages/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.9 on 2025-04-06 16:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('info', models.CharField(default='', max_length=100)), + ('price', models.IntegerField(blank=True, null=True)), + ], + ), + ] diff --git a/apps/pages/migrations/__init__.py b/apps/pages/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pages/models.py b/apps/pages/models.py new file mode 100644 index 0000000..3a239d4 --- /dev/null +++ b/apps/pages/models.py @@ -0,0 +1,12 @@ +from django.db import models + +# Create your models here. + +class Product(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length = 100) + info = models.CharField(max_length = 100, default = '') + price = models.IntegerField(blank=True, null=True) + + def __str__(self): + return self.name diff --git a/apps/pages/tests.py b/apps/pages/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/pages/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/pages/urls.py b/apps/pages/urls.py new file mode 100644 index 0000000..88a9cac --- /dev/null +++ b/apps/pages/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('', views.index, name='index'), +] diff --git a/apps/pages/views.py b/apps/pages/views.py new file mode 100644 index 0000000..ad772ae --- /dev/null +++ b/apps/pages/views.py @@ -0,0 +1,9 @@ +from django.shortcuts import render +from django.http import HttpResponse + +# Create your views here. + +def index(request): + + # Page from the theme + return render(request, 'pages/index.html', {'segment': 'dashboard'}) diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..8832d8e --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for core project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_asgi_application() diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..9974600 --- /dev/null +++ b/config/settings.py @@ -0,0 +1,207 @@ +""" +Django settings for core project. + +Generated by 'django-admin startproject' using Django 4.1.2. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.1/ref/settings/ +""" + +import os, random, string +from pathlib import Path +from dotenv import load_dotenv +from str2bool import str2bool + +load_dotenv() # take environment variables from .env. + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get('SECRET_KEY', 'Super_Secr3t_9999') + +# Enable/Disable DEBUG Mode +DEBUG = str2bool(os.environ.get('DEBUG')) +#print(' DEBUG -> ' + str(DEBUG) ) + +# Docker HOST +ALLOWED_HOSTS = ['*'] + +# Add here your deployment HOSTS +CSRF_TRUSTED_ORIGINS = ['http://localhost:8000', 'http://localhost:5085', 'http://127.0.0.1:8000', 'http://127.0.0.1:5085'] + +RENDER_EXTERNAL_HOSTNAME = os.environ.get('RENDER_EXTERNAL_HOSTNAME') +if RENDER_EXTERNAL_HOSTNAME: + ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME) + +# Application definition + +INSTALLED_APPS = [ + 'jazzmin', + 'admin_material.apps.AdminMaterialDashboardConfig', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + + # Serve UI pages + "apps.pages", + + # Dynamic DT + "apps.dyn_dt", + + # Dynamic API + "apps.dyn_api", + + # Charts + "apps.charts", + + # Tooling API-GEN + 'rest_framework', # Include DRF # <-- NEW + 'rest_framework.authtoken', # Include DRF Auth # <-- NEW +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +UI_TEMPLATES = os.path.join(BASE_DIR, 'templates') + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [UI_TEMPLATES], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "config.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/4.1/ref/settings/#databases + +DB_ENGINE = os.getenv('DB_ENGINE' , None) +DB_USERNAME = os.getenv('DB_USERNAME' , None) +DB_PASS = os.getenv('DB_PASS' , None) +DB_HOST = os.getenv('DB_HOST' , None) +DB_PORT = os.getenv('DB_PORT' , None) +DB_NAME = os.getenv('DB_NAME' , None) + +if DB_ENGINE and DB_NAME and DB_USERNAME: + DATABASES = { + 'default': { + 'ENGINE' : 'django.db.backends.' + DB_ENGINE, + 'NAME' : DB_NAME, + 'USER' : DB_USERNAME, + 'PASSWORD': DB_PASS, + 'HOST' : DB_HOST, + 'PORT' : DB_PORT, + }, + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'db.sqlite3', + } + } + +# Password validation +# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.1/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.1/howto/static-files/ + +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') + +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, 'static'), +) + +#if not DEBUG: +# STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGIN_REDIRECT_URL = '/' +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + + +# ### DYNAMIC_DATATB Settings ### +DYNAMIC_DATATB = { + # SLUG -> Import_PATH + 'product' : "apps.pages.models.Product", +} +######################################## + +# Syntax: URI -> Import_PATH +DYNAMIC_API = { + # SLUG -> Import_PATH + 'product' : "apps.pages.models.Product", +} + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.TokenAuthentication', + ], +} +######################################## diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..adba413 --- /dev/null +++ b/config/urls.py @@ -0,0 +1,26 @@ +"""core URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + path('', include('apps.pages.urls')), + path('', include('apps.dyn_dt.urls')), + path('', include('apps.dyn_api.urls')), + path('charts/', include('apps.charts.urls')), + path("admin/", admin.site.urls), + path("", include('admin_material.urls')) +] diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..6194205 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for core project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + +application = get_wsgi_application() diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/static/gulpfile.js b/static/gulpfile.js new file mode 100644 index 0000000..90f9055 --- /dev/null +++ b/static/gulpfile.js @@ -0,0 +1,59 @@ +/* + +========================================================= +* AppSeed - Simple SCSS compiler via Gulp +========================================================= + +*/ + +var autoprefixer = require('gulp-autoprefixer'); +var browserSync = require('browser-sync').create(); +var cleanCss = require('gulp-clean-css'); +var gulp = require('gulp'); +const npmDist = require('gulp-npm-dist'); +var sass = require('gulp-sass')(require('node-sass')); +var wait = require('gulp-wait'); +var sourcemaps = require('gulp-sourcemaps'); +var rename = require("gulp-rename"); + +// Define COMMON paths + +const paths = { + src: { + base: './', + css: './css', + scss: './scss', + node_modules: './node_modules/', + vendor: './vendor' + } +}; + +// Compile SCSS +gulp.task('scss', function() { + return gulp.src([paths.src.scss + '/material-dashboard.scss']) + .pipe(wait(500)) + .pipe(sourcemaps.init()) + .pipe(sass().on('error', sass.logError)) + .pipe(autoprefixer({ + overrideBrowserslist: ['> 1%'] + })) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest(paths.src.css)) + .pipe(browserSync.stream()); +}); + +// Minify CSS +gulp.task('minify:css', function() { + return gulp.src([ + paths.src.css + '/material-dashboard.css' + ]) + .pipe(cleanCss()) + .pipe(rename(function(path) { + // Updates the object in-place + path.extname = ".min.css"; + })) + .pipe(gulp.dest(paths.src.css)) +}); + +// Default Task: Compile SCSS and minify the result +gulp.task('default', gulp.series('scss', 'minify:css')); diff --git a/static/img/csv.png b/static/img/csv.png new file mode 100644 index 0000000..330065b Binary files /dev/null and b/static/img/csv.png differ diff --git a/static/img/export.png b/static/img/export.png new file mode 100644 index 0000000..25cac3c Binary files /dev/null and b/static/img/export.png differ diff --git a/static/package.json b/static/package.json new file mode 100644 index 0000000..7849559 --- /dev/null +++ b/static/package.json @@ -0,0 +1,44 @@ +{ + "name": "appseed-generic", + "version": "1.0.0", + "description": "Template", + "main": "gulpfile.js", + "author": "ApPSeed", + "keywords": [ + "css", + "sass", + "gulp", + "web" + ], + "homepage": "https://appseed.us", + "repository": { + "type": "git", + "url": "https://github.com/app-generator/app-generator" + }, + "bugs": { + "email": "support@appseed.us" + }, + "license": "MIT License", + "devDependencies": { + "browser-sync": "^2.27.4", + "del": "^6.0.0", + "gulp": "^4.0.2", + "gulp-autoprefixer": "^8.0.0", + "gulp-clean-css": "^4.3.0", + "gulp-cssbeautify": "^3.0.0", + "node-sass": "^6.0.1", + "gulp-file-include": "^2.3.0", + "gulp-header": "^2.0.9", + "gulp-htmlmin": "^5.0.1", + "gulp-npm-dist": "^1.0.3", + "gulp-plumber": "^1.2.1", + "gulp-rename": "^2.0.0", + "gulp-sass": "^5.0.0", + "gulp-sourcemaps": "^3.0.0", + "gulp-uglify": "^3.0.2", + "gulp-wait": "^0.0.2", + "merge-stream": "^2.0.0" + }, + "dependencies": { + } +} \ No newline at end of file diff --git a/templates/.gitkeep b/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/charts/index.html b/templates/charts/index.html new file mode 100644 index 0000000..0aff9fa --- /dev/null +++ b/templates/charts/index.html @@ -0,0 +1,63 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block title %}Charts{% endblock title %} + +{% block content %} +
+
+ +
+
+
+
Bar Chart
+
+
+
+
+
+
+ + + +
+
+
+
Pie Chart
+
+
+
+
+
+
+ +
+
+{% endblock content %} + +{% block extra_js %} + + +{% endblock extra_js %} diff --git a/templates/dyn_api/index.html b/templates/dyn_api/index.html new file mode 100644 index 0000000..3e47f35 --- /dev/null +++ b/templates/dyn_api/index.html @@ -0,0 +1,38 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block title %} Dynamic APIs {% endblock title %} + +{% block content %} + +
+
+
+
+
+
+ Available Routes - defined in settings.DYNAMIC_API - Read Documentation. +
+
+
+
    + {% for link in routes %} +
  • + {{ link }} +
  • + {% endfor %} +
+
+
+
+
+
+ +{% endblock content %} + + +{% block extra_js %} + + + +{% endblock extra_js %} \ No newline at end of file diff --git a/templates/dyn_dt/index.html b/templates/dyn_dt/index.html new file mode 100644 index 0000000..fa70e6f --- /dev/null +++ b/templates/dyn_dt/index.html @@ -0,0 +1,36 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block title %} {% if page_title %} page_title {% else %} Dynamic DataTables {% endif %} {% endblock title %} + +{% block content %} + +
+
+
+
+
+
+ Available Routes - defined in settings.DYNAMIC_DATATB - Read Documentation. +
+
+
+
    + {% for link in routes %} +
  • + {{ link }} +
  • + {% endfor %} +
+
+
+
+
+
+ +{% endblock content %} + + +{% block extra_js %} + +{% endblock extra_js %} \ No newline at end of file diff --git a/templates/dyn_dt/items-table.html b/templates/dyn_dt/items-table.html new file mode 100644 index 0000000..032889b --- /dev/null +++ b/templates/dyn_dt/items-table.html @@ -0,0 +1,22 @@ +{% load get_attribute %} + +
+ + + + {% for field in db_field_names %} + + {% endfor %} + + + + {% for item in items %} + + {% for field_name in db_field_names %} + + {% endfor %} + + {% endfor %} + +
{{ field }}
{{ item|getattribute:field_name }}
+
\ No newline at end of file diff --git a/templates/dyn_dt/model.html b/templates/dyn_dt/model.html new file mode 100644 index 0000000..b9fc5b2 --- /dev/null +++ b/templates/dyn_dt/model.html @@ -0,0 +1,585 @@ +{% extends "layouts/base.html" %} +{% load static get_attribute %} + +{% block title %} {% if page_title %} {{page_title}} {% else %} Dynamic DataTables {% endif %} {% endblock title %} + +{% block extrastyle %} + + + +{% endblock extrastyle %} + +{% block content %} +
+
+
+
+
+ +
+ +
+
+ +
+
+
+
+
+ {% csrf_token %} + +
+
+ + img + +
+ {% if request.user.is_authenticated %} +
+ +
+ {% endif %} +
+
+
+ +
+
+ {% csrf_token %} + +
+

Filters

+ +
+ +
+ {% if filter_instance %} + {% for filter_data in filter_instance %} +
+
+ + +
+ X +
+ {% endfor %} + {% endif %} +
+ +
+ +
+
+ + + + {% for field in db_field_names %} + + {% endfor %} + + + + {% for item in items %} + + {% for field_name in db_field_names %} + + {% endfor %} + + {% if request.user.is_authenticated %} + + {% else %} + + {% endif %} + + + + + + + + + + + + {% endfor %} + +
{{ field }}
{{ item|getattribute:field_name }} + + edit + + + delete + + + + visibility + +
+
+
+ {% if items.has_other_pages %} + + {% endif %} +
+
+ + + + + + + +
+
+
+
+ +{% endblock content %} + + +{% block extra_js %} + + + + + + + +{% endblock extra_js %} \ No newline at end of file diff --git a/templates/includes/sidebar.html b/templates/includes/sidebar.html new file mode 100644 index 0000000..9f22630 --- /dev/null +++ b/templates/includes/sidebar.html @@ -0,0 +1,165 @@ +{% load i18n static admin_material %} + + \ No newline at end of file diff --git a/templates/pages/billing.html b/templates/pages/billing.html new file mode 100644 index 0000000..24bc212 --- /dev/null +++ b/templates/pages/billing.html @@ -0,0 +1,321 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+
+
+
+
+
+
+ pattern-tree + +
+ wifi +
4562   1122   4594   7852
+
+
+
+

Card Holder

+
Jack Peterson
+
+
+

Expires

+
11/22
+
+
+
+ logo +
+
+
+
+
+
+
+
+
+
+
+
+ account_balance +
+
+
+
Salary
+ Belong Interactive +
+
+$2000
+
+
+
+
+
+
+
+ account_balance_wallet +
+
+
+
Paypal
+ Freelance Payment +
+
$455.00
+
+
+
+
+
+
+
+
+
+
+
Payment Method
+
+ +
+
+
+
+
+
+ logo +
****   ****   ****   7852
+ edit +
+
+
+
+ logo +
****   ****   ****   5248
+ edit +
+
+
+
+
+
+
+
+
+
+
+
+
+
Invoices
+
+
+ +
+
+
+
+
    +
  • +
    +
    March, 01, 2020
    + #MS-415646 +
    +
    + $180 + +
    +
  • +
  • +
    +
    February, 10, 2021
    + #RV-126749 +
    +
    + $250 + +
    +
  • +
  • +
    +
    April, 05, 2020
    + #FB-212562 +
    +
    + $560 + +
    +
  • +
  • +
    +
    June, 25, 2019
    + #QW-103578 +
    +
    + $120 + +
    +
  • +
  • +
    +
    March, 01, 2019
    + #AR-803481 +
    +
    + $300 + +
    +
  • +
+
+
+
+
+
+
+
+
+
Billing Information
+
+
+
    +
  • +
    +
    Oliver Liam
    + Company Name: Viking Burrito + Email Address: oliver@burrito.com + VAT Number: FRB1235476 +
    + +
  • +
  • +
    +
    Lucas Harper
    + Company Name: Stone Tech Zone + Email Address: lucas@stone-tech.com + VAT Number: FRB1235476 +
    + +
  • +
  • +
    +
    Ethan James
    + Company Name: Fiber Notion + Email Address: ethan@fiber.com + VAT Number: FRB1235476 +
    + +
  • +
+
+
+
+
+
+
+
+
+
Your Transaction's
+
+
+ date_range + 23 - 30 March 2020 +
+
+
+
+
Newest
+
    +
  • +
    + +
    +
    Netflix
    + 27 March 2020, at 12:30 PM +
    +
    +
    + - $ 2,500 +
    +
  • +
  • +
    + +
    +
    Apple
    + 27 March 2020, at 04:30 AM +
    +
    +
    + + $ 2,000 +
    +
  • +
+
Yesterday
+
    +
  • +
    + +
    +
    Stripe
    + 26 March 2020, at 13:45 PM +
    +
    +
    + + $ 750 +
    +
  • +
  • +
    + +
    +
    HubSpot
    + 26 March 2020, at 12:30 PM +
    +
    +
    + + $ 1,000 +
    +
  • +
  • +
    + +
    +
    Creative Tim
    + 26 March 2020, at 08:30 AM +
    +
    +
    + + $ 2,500 +
    +
  • +
  • +
    + +
    +
    Webflow
    + 26 March 2020, at 05:00 AM +
    +
    +
    + Pending +
    +
  • +
+
+
+
+
+ {% include "includes/footer.html" %} +
+ +{% endblock content %} \ No newline at end of file diff --git a/templates/pages/icons.html b/templates/pages/icons.html new file mode 100644 index 0000000..acf49a5 --- /dev/null +++ b/templates/pages/icons.html @@ -0,0 +1,28 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+
+
+
+
+
+
Material Design Icons
+

Handcrafted by our friends from + Google +

+
+
+
+

Through most of the examples in this dashboard, we have used the default Icons for the Material Design provided by Google.

+ face +
+
+
+
+ {% include "includes/footer.html" %} +
+ +{% endblock content %} diff --git a/templates/pages/index.html b/templates/pages/index.html new file mode 100644 index 0000000..341ad2d --- /dev/null +++ b/templates/pages/index.html @@ -0,0 +1,735 @@ +{% extends "layouts/base.html" %} +{% load static %} + + +{% block content %} + +
+
+
+

Dashboard

+

+ Check the sales, value and bounce rate by country. +

+
+
+
+
+
+
+

Today's Money

+

$53k

+
+
+ weekend +
+
+
+
+ +
+
+
+
+
+
+
+

Today's Users

+

2300

+
+
+ person +
+
+
+
+ +
+
+
+
+
+
+
+

Ads Views

+

3,462

+
+
+ leaderboard +
+
+
+
+ +
+
+
+
+
+
+
+

Sales

+

$103,430

+
+
+ weekend +
+
+
+
+ +
+
+
+
+
+
+
+
Website Views
+

Last Campaign Performance

+
+
+ +
+
+
+
+ schedule +

campaign sent 2 days ago

+
+
+
+
+
+
+
+
Daily Sales
+

(+15%) increase in today sales.

+
+
+ +
+
+
+
+ schedule +

updated 4 min ago

+
+
+
+
+
+
+
+
Completed Tasks
+

Last Campaign Performance

+
+
+ +
+
+
+
+ schedule +

just updated

+
+
+
+
+
+
+
+
+
+
+
+
Projects
+

+ + 30 done this month +

+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CompaniesMembersBudgetCompletion
+
+
+ xd +
+
+
Material XD Version
+
+
+
+ + + $14,000 + +
+
+
+ 60% +
+
+
+
+
+
+
+
+
+ atlassian +
+
+
Add Progress Track
+
+
+
+ + + $3,000 + +
+
+
+ 10% +
+
+
+
+
+
+
+
+
+ team7 +
+
+
Fix Platform Errors
+
+
+
+ + + Not set + +
+
+
+ 100% +
+
+
+
+
+
+
+
+
+ spotify +
+
+
Launch our Mobile App
+
+
+
+ + + $20,500 + +
+
+
+ 100% +
+
+
+
+
+
+
+
+
+ jira +
+
+
Add the New Pricing Page
+
+
+
+
+ + user5 + +
+
+ $500 + +
+
+
+ 25% +
+
+
+
+
+
+
+
+
+ invision +
+
+
Redesign New Online Shop
+
+
+
+ + + $2,000 + +
+
+
+ 40% +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Orders overview
+

+ + 24% this month +

+
+
+
+
+ + notifications + +
+
$2400, Design changes
+

22 DEC 7:20 PM

+
+
+
+ + code + +
+
New order #1832412
+

21 DEC 11 PM

+
+
+
+ + shopping_cart + +
+
Server payments for April
+

21 DEC 9:34 PM

+
+
+
+ + credit_card + +
+
New card added for order #4395133
+

20 DEC 2:20 AM

+
+
+
+ + key + +
+
Unlock packages for development
+

18 DEC 4:54 AM

+
+
+
+ + payments + +
+
New order #9583120
+

17 DEC

+
+
+
+
+
+
+
+ {% include "includes/footer.html" %} +
+ + {% endblock content %} + + {% block extra_js %} + + + + +{% endblock extra_js %} \ No newline at end of file diff --git a/templates/pages/map.html b/templates/pages/map.html new file mode 100644 index 0000000..de399a7 --- /dev/null +++ b/templates/pages/map.html @@ -0,0 +1,79 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+
+
+
+
+
+
Vector Map
+
+
+
+
+
+
+
+
+ {% include "includes/footer.html" %} +
+ +{% endblock content %} + +{% block extra_js %} + + + + + +{% endblock extra_js %} \ No newline at end of file diff --git a/templates/pages/notifications.html b/templates/pages/notifications.html new file mode 100644 index 0000000..1891cd3 --- /dev/null +++ b/templates/pages/notifications.html @@ -0,0 +1,152 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+
+
+
+
+
Alerts
+
+
+ + + + + + + + +
+
+
+
+
Notifications
+

+ Notifications on this page use Toasts from Bootstrap. Read more details here. +

+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+ + + + +
+ {% include "includes/footer.html" %} +
+ +{% endblock content %} \ No newline at end of file diff --git a/templates/pages/profile.html b/templates/pages/profile.html new file mode 100644 index 0000000..09cc8fb --- /dev/null +++ b/templates/pages/profile.html @@ -0,0 +1,370 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+ +
+
+
+
+ profile_image +
+
+
+
+
+ Richard Davis +
+

+ CEO / Co-Founder +

+
+
+ +
+
+
+
+
+
+
Platform Settings
+
+
+
Account
+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
Application
+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
+
+
+
+
+
+
+
+
Profile Information
+
+
+ + + +
+
+
+
+

+ Hi, I’m Alec Thompson, Decisions: If you can’t decide, the answer is no. If two equally difficult paths, choose the one more painful in the short term (pain avoidance is creating an illusion of equality). +

+
+
    +
  • Full Name:   Alec M. Thompson
  • +
  • Mobile:   (44) 123 1234 123
  • +
  • Email:   alecthompson@mail.com
  • +
  • Location:   USA
  • +
  • + Social:   + + + + + + + + + +
  • +
+
+
+
+
+
+
+
Conversations
+
+
+
    +
  • +
    + kal +
    +
    +
    Sophie B.
    +

    Hi! I need more information..

    +
    + Reply +
  • +
  • +
    + kal +
    +
    +
    Anne Marie
    +

    Awesome work, can you..

    +
    + Reply +
  • +
  • +
    + kal +
    +
    +
    Ivanna
    +

    About files I can..

    +
    + Reply +
  • +
  • +
    + kal +
    +
    +
    Peterson
    +

    Have a great afternoon..

    +
    + Reply +
  • +
  • +
    + kal +
    +
    +
    Nick Daniel
    +

    Hi! I need more information..

    +
    + Reply +
  • +
+
+
+
+
+
+
Projects
+

Architects design houses

+
+
+
+
+
+ + img-blur-shadow + +
+
+

Project #2

+ +
+ Modern +
+
+

+ As Uber works through a huge amount of internal management turmoil. +

+
+ + +
+
+
+
+
+
+
+ + img-blur-shadow + +
+
+

Project #1

+ +
+ Scandinavian +
+
+

+ Music is something that every person has his or her own specific opinion about. +

+
+ + +
+
+
+
+
+
+
+ + img-blur-shadow + +
+
+

Project #3

+ +
+ Minimalist +
+
+

+ Different people have different taste, and various types of music. Music is life. +

+
+ + +
+
+
+
+
+
+
+ + img-blur-shadow + +
+
+

Project #4

+ +
+ Gothic +
+
+

+ Why would anyone pick blue over pink? Pink is obviously a better color. +

+
+ + +
+
+
+
+
+
+
+
+
+
+ + {% include "includes/footer.html" %} + + +{% endblock content %} \ No newline at end of file diff --git a/templates/pages/rtl.html b/templates/pages/rtl.html new file mode 100644 index 0000000..394180c --- /dev/null +++ b/templates/pages/rtl.html @@ -0,0 +1,1009 @@ +{% load static %} + + + + + {% include "includes/head.html" %} + + + + +
+ + + +
+
+
+
+
+
+ weekend +
+
+

أموال اليوم

+

$53k

+
+
+
+ +
+
+
+
+
+
+ leaderboard +
+
+

مستخدمو اليوم

+

2,300

+
+
+
+ +
+
+
+
+
+
+ store +
+
+

عملاء جدد

+

+ -2% + +3,462 +

+
+
+
+ +
+
+
+
+
+
+ weekend +
+
+

مبيعات

+

$103,430

+
+
+
+ +
+
+
+
+
+
+
+
مشاهدات الموقع
+

آخر أداء للحملة

+
+
+ +
+
+
+
+ schedule +

لحملة أرسلت قبل يومين

+
+
+
+
+
+
+
+
المبيعات اليومية
+

(+15%) زيادة في مبيعات اليوم

+
+
+ +
+
+
+
+ schedule +

لحملة أرسلت قبل يومين

+
+
+
+
+
+
+
+
المهام المكتملة
+

آخر أداء للحملة

+
+
+ +
+
+
+
+ schedule +

تم تحديثه للتو

+
+
+
+
+
+
+
+
+
+
+
+
المشاريع
+

+ + 30 انتهى هذا الشهر +

+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
المشروعأعضاءميزانيةإكمال
+
+
+ +
+
+
Material XD الإصدار
+
+
+
+ + + $14,000 + +
+
+
+ 60% +
+
+
+
+
+
+
+
+
+ +
+
+
أضف مسار التقدم إلى التطبيق الداخلي
+
+
+
+ + + $3,000 + +
+
+
+ 10% +
+
+
+
+
+
+
+
+
+ +
+
+
إصلاح أخطاء النظام الأساسي
+
+
+
+ + + غير مضبوط + +
+
+
+ 100% +
+
+
+
+
+
+
+
+
+ +
+
+
إطلاق تطبيق الهاتف المحمول الخاص بنا
+
+
+
+ + + $20,500 + +
+
+
+ 100% +
+
+
+
+
+
+
+
+
+ +
+
+
أضف صفحة التسعير الجديدة
+
+
+
+
+ + Image placeholder + +
+
+ $500 + +
+
+
+ 25% +
+
+
+
+
+
+
+
+
+ +
+
+
إعادة تصميم متجر جديد على الإنترنت
+
+
+
+ + + $2,000 + +
+
+
+ 40% +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
نظرة عامة على الطلبات
+

+ + 24% هذا الشهر +

+
+
+
+
+ + notifications + +
+
$2400, تغييرات في التصميم
+

22 DEC 7:20 PM

+
+
+
+ + code + +
+
طلب جديد #1832412
+

21 DEC 11 PM

+
+
+
+ + shopping_cart + +
+
مدفوعات الخادم لشهر أبريل
+

21 DEC 9:34 PM

+
+
+
+ + credit_card + +
+
تمت إضافة بطاقة جديدة للطلب #4395133
+

20 DEC 2:20 AM

+
+
+
+ + key + +
+
فتح الحزم من أجل التطوير
+

18 DEC 4:54 AM

+
+
+
+ + payments + +
+
طلب جديد #9583120
+

17 DEC

+
+
+
+
+
+
+
+ +
+
+
+ + settings + +
+
+
+
Material UI Configurator
+

See our dashboard options.

+
+
+ +
+ +
+
+
+ +
+
Sidebar Colors
+
+ +
+ + + + + + +
+
+ +
+
Sidenav Type
+

Choose between different sidenav types.

+
+
+ + + +
+

You can change the sidenav type just on desktop view.

+ +
+
Navbar Fixed
+
+ +
+
+
+
+
Light / Dark
+
+ +
+
+
+ Download + Documentation +
+
+
+ + + {% include "includes/scripts.html" %} + + + + + + + \ No newline at end of file diff --git a/templates/pages/sign-in.html b/templates/pages/sign-in.html new file mode 100644 index 0000000..d4bd30f --- /dev/null +++ b/templates/pages/sign-in.html @@ -0,0 +1,96 @@ +{% extends "layouts/base-auth.html" %} +{% load static %} + +{% block body %} bg-gray-200 {% endblock body %} + +{% block content %} + +
+ +
+ +{% endblock content %} diff --git a/templates/pages/sign-up.html b/templates/pages/sign-up.html new file mode 100644 index 0000000..0c34cc0 --- /dev/null +++ b/templates/pages/sign-up.html @@ -0,0 +1,56 @@ +{% extends "layouts/base-auth.html" %} +{% load static %} + +{% block content %} + +
+
+ +
+
+ +{% endblock content %} diff --git a/templates/pages/tables.html b/templates/pages/tables.html new file mode 100644 index 0000000..0d4a56a --- /dev/null +++ b/templates/pages/tables.html @@ -0,0 +1,432 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+
+
+
+
+
+
Authors table
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AuthorFunctionStatusEmployed
+
+
+ user1 +
+
+
John Michael
+

john@creative-tim.com

+
+
+
+

Manager

+

Organization

+
+ Online + + 23/04/18 + + + Edit + +
+
+
+ user2 +
+
+
Alexa Liras
+

alexa@creative-tim.com

+
+
+
+

Programator

+

Developer

+
+ Offline + + 11/01/19 + + + Edit + +
+
+
+ user3 +
+
+
Laurent Perrier
+

laurent@creative-tim.com

+
+
+
+

Executive

+

Projects

+
+ Online + + 19/09/17 + + + Edit + +
+
+
+ user4 +
+
+
Michael Levi
+

michael@creative-tim.com

+
+
+
+

Programator

+

Developer

+
+ Online + + 24/12/08 + + + Edit + +
+
+
+ user5 +
+
+
Richard Gran
+

richard@creative-tim.com

+
+
+
+

Manager

+

Executive

+
+ Offline + + 04/10/21 + + + Edit + +
+
+
+ user6 +
+
+
Miriam Eric
+

miriam@creative-tim.com

+
+
+
+

Programator

+

Developer

+
+ Offline + + 14/09/20 + + + Edit + +
+
+
+
+
+
+
+
+
+
+
+
Projects table
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProjectBudgetStatusCompletion
+
+
+ spotify +
+
+
Asana
+
+
+
+

$2,500

+
+ working + +
+ 60% +
+
+
+
+
+
+
+ +
+
+
+ invision +
+
+
Github
+
+
+
+

$5,000

+
+ done + +
+ 100% +
+
+
+
+
+
+
+ +
+
+
+ jira +
+
+
Atlassian
+
+
+
+

$3,400

+
+ canceled + +
+ 30% +
+
+
+
+
+
+
+ +
+
+
+ webdev +
+
+
Bootstrap
+
+
+
+

$14,000

+
+ working + +
+ 80% +
+
+
+
+
+
+
+ +
+
+
+ slack +
+
+
Slack
+
+
+
+

$1,000

+
+ canceled + +
+ 0% +
+
+
+
+
+
+
+ +
+
+
+ xd +
+
+
Devto
+
+
+
+

$2,300

+
+ done + +
+ 100% +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ {% include "includes/footer.html" %} +
+ +{% endblock content %} diff --git a/templates/pages/template.html b/templates/pages/template.html new file mode 100644 index 0000000..369353b --- /dev/null +++ b/templates/pages/template.html @@ -0,0 +1,10 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+ {% include "includes/footer.html" %} +
+ +{% endblock content %} \ No newline at end of file diff --git a/templates/pages/typography.html b/templates/pages/typography.html new file mode 100644 index 0000000..e194648 --- /dev/null +++ b/templates/pages/typography.html @@ -0,0 +1,47 @@ +{% extends "layouts/base.html" %} +{% load static %} + +{% block content %} + +
+
+
+
+
+
+
Material Dashboard Heading
+

Created using Roboto Slab Font Family +

+
+
+
+

h1. Bootstrap heading

+

h2. Bootstrap heading

+

h3. Bootstrap heading

+

h4. Bootstrap heading

+
h5. Bootstrap heading
+
h6. Bootstrap heading
+

You can use the mark tag to highlight text.

+

This line of text is meant to be treated as deleted text.

+

This line of text is meant to be treated as no longer accurate.

+

This line of text is meant to be treated as an addition to the document.

+

This line of text will render as underlined

+

This line of text is meant to be treated as fine print.

+

This line rendered as bold text.

+

This line rendered as italicized text.

+
+
+

Because I’m here to follow my dreams and inspire other people to follow their dreams, too.

+
+ +
+
+
+
+
+ {% include "includes/footer.html" %} +
+ +{% endblock content %} diff --git a/templates/pages/virtual-reality.html b/templates/pages/virtual-reality.html new file mode 100644 index 0000000..9f7c45c --- /dev/null +++ b/templates/pages/virtual-reality.html @@ -0,0 +1,165 @@ + +{% load static %} + + + + {% include "includes/head.html" %} + + + +
+ + {% include "includes/navigation.html" %} + +
+
+ {% include "includes/sidebar.html" %} + +
+
+
+
+
+ + Image placeholder + + + + +
+
+
+
+

28°C

+
Cloudy
+
+
+ image sun +
+
+
+
+
+
+
+
08:00
+
Synk up with Mark + Hangouts +
+
+
+
+
09:30
+
Gym
+ World Class +
+
+
+
+
11:00
+
Design Review
+ Zoom +
+
+
+ + expand_more + +
+
+
+
+
+
+
To Do
+
+

7

+

items

+
+
+

Shopping

+

Meeting

+
+ + expand_more + +
+
+
+
+

Emails (21)

+ + Check + +
+
+
+
+
+
+
+
+
+
Night Jazz
+

Gary Coleman

+
+ + + +
+
+
+
+
+
+
+

Messages

+ +
+
+
+
+
+
+
+
+
+
+
+ + {% include "includes/footer.html" %} + + {% include "includes/configurator.html" %} + + {% include "includes/scripts.html" %} + + + + \ No newline at end of file