├── .env.example ├── .gitignore ├── Procfile ├── README.md ├── backend ├── __init__.py ├── apps │ ├── __init__.py │ └── users │ │ ├── __init__.py │ │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py ├── pagination.py ├── permissions.py ├── settings │ ├── __init__.py │ ├── common.py │ ├── devl.py │ └── production.py ├── static │ ├── adminlte │ │ └── img │ │ │ ├── avatar.png │ │ │ ├── avatar04.png │ │ │ ├── avatar2.png │ │ │ ├── avatar3.png │ │ │ ├── avatar5.png │ │ │ ├── boxed-bg.jpg │ │ │ ├── boxed-bg.png │ │ │ ├── credit │ │ │ ├── american-express.png │ │ │ ├── cirrus.png │ │ │ ├── mastercard.png │ │ │ ├── mestro.png │ │ │ ├── paypal.png │ │ │ ├── paypal2.png │ │ │ └── visa.png │ │ │ ├── default-50x50.gif │ │ │ ├── icons.png │ │ │ ├── photo1.png │ │ │ ├── photo2.png │ │ │ ├── photo3.jpg │ │ │ ├── photo4.jpg │ │ │ ├── user1-128x128.jpg │ │ │ ├── user2-160x160.jpg │ │ │ ├── user3-128x128.jpg │ │ │ ├── user4-128x128.jpg │ │ │ ├── user5-128x128.jpg │ │ │ ├── user6-128x128.jpg │ │ │ ├── user7-128x128.jpg │ │ │ └── user8-128x128.jpg │ ├── favicon.ico │ └── humans.txt ├── templates │ ├── backend │ │ ├── app.html │ │ └── test.html │ └── registration │ │ ├── base.html │ │ └── login.html ├── urls.py ├── views.py └── wsgi.py ├── db.sqlite3 ├── dev.txt ├── frontend ├── .babelrc ├── .eslintrc ├── .gitignore ├── README.md ├── build │ ├── admin-lte │ │ └── dist │ │ │ └── img │ │ │ └── boxed-bg.jpg │ ├── bootstrap │ │ └── dist │ │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ ├── emptyFile.txt │ ├── font-awesome │ │ └── fonts │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── js │ │ ├── bundle.js │ │ └── vendor.bundle.js │ └── stylesheets.css ├── package.json ├── src │ ├── adminlte │ │ ├── Box │ │ │ ├── Body.js │ │ │ ├── Header.js │ │ │ ├── Title.js │ │ │ ├── Tools.js │ │ │ ├── Wrapper.js │ │ │ └── index.js │ │ ├── Content.js │ │ ├── ContentHeader.js │ │ ├── ContentWrapper.js │ │ ├── ControlSidebar.js │ │ ├── MainHeader │ │ │ ├── ControlSidebarToggle.js │ │ │ ├── Logo.js │ │ │ ├── MainSidebarToggle.js │ │ │ ├── Menu.js │ │ │ ├── Nav.js │ │ │ ├── Navbar.js │ │ │ ├── UserMenu.js │ │ │ ├── Wrapper.js │ │ │ └── index.js │ │ ├── MainSidebar │ │ │ ├── Form.js │ │ │ ├── Menu.js │ │ │ ├── UserPanel.js │ │ │ ├── Wrapper.js │ │ │ └── index.js │ │ ├── Wrapper.js │ │ ├── actions.js │ │ ├── constants.js │ │ ├── index.js │ │ ├── model.js │ │ ├── reducers.js │ │ └── selectors.js │ ├── app │ │ ├── Root.js │ │ ├── actions │ │ │ ├── alerts.js │ │ │ └── collection.js │ │ ├── components │ │ │ ├── DeleteButton.js │ │ │ ├── EmptyComponent.js │ │ │ ├── LinkedListGroup.js │ │ │ ├── Pagination.js │ │ │ ├── PaginationInfo.js │ │ │ ├── RefreshButton.js │ │ │ ├── RouteNotFound.js │ │ │ ├── SearchBox.js │ │ │ ├── alerts │ │ │ │ ├── InternalServerError.js │ │ │ │ └── SessionExpired.js │ │ │ ├── higherOrder │ │ │ │ ├── README.md │ │ │ │ ├── create.js │ │ │ │ ├── edit.js │ │ │ │ ├── findModel.js │ │ │ │ ├── hasPermission.js │ │ │ │ └── query.js │ │ │ └── list │ │ │ │ ├── Box.js │ │ │ │ └── Container.js │ │ ├── configureStore.js │ │ ├── constants │ │ │ ├── alerts.js │ │ │ └── http.js │ │ ├── history.js │ │ ├── layouts │ │ │ └── Admin │ │ │ │ ├── MainFooter.js │ │ │ │ ├── MainHeader.js │ │ │ │ ├── MainSidebar.js │ │ │ │ └── index.js │ │ ├── reducers │ │ │ ├── alerts.js │ │ │ └── collection.js │ │ ├── rootReducer.js │ │ ├── stylesheets │ │ │ ├── app.css │ │ │ ├── index.less │ │ │ └── nprogress.less │ │ ├── urls.js │ │ ├── users │ │ │ ├── components │ │ │ │ ├── CreateForm.js │ │ │ │ ├── EditForm.js │ │ │ │ ├── Model.js │ │ │ │ └── QueryForm.js │ │ │ ├── constants.js │ │ │ ├── models.js │ │ │ ├── reducers.js │ │ │ ├── urls.js │ │ │ └── views │ │ │ │ ├── Detail.js │ │ │ │ ├── List.js │ │ │ │ └── Tabs.js │ │ └── utils │ │ │ ├── http.js │ │ │ ├── isFilterActive.js │ │ │ └── viewportDimensions.js │ └── index.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── manage.py ├── requirements.txt └── runtime.txt /.env.example: -------------------------------------------------------------------------------- 1 | WEB_CONCURRENCY=2 2 | SECRET_KEY=t9uk7z8vrm@43978@*v42dvr**9+qe&+!s!51u!777#o66lbx4 3 | DB_USER=user 4 | DB_NAME=dbname 5 | DB_PASSWORD='' 6 | DB_HOST=127.0.0.1 7 | DB_PORT=5432 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | staticfiles 4 | .env 5 | .idea 6 | backend/settings/local.py 7 | .idea/workspace.xml -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn backend.wsgi 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Heroku Django Starter Template 2 | 3 | An utterly fantastic project starter template for Django 1.11. 4 | 5 | ## Features 6 | 7 | - Production-ready configuration for Static Files, Database Settings, Gunicorn, etc. 8 | - Enhancements to Django's static file serving functionality via WhiteNoise. 9 | - Latest Python 3.6 runtime environment. 10 | 11 | ## How to Use 12 | 13 | To use this project, follow these steps: 14 | 15 | 1. Create your working environment. 16 | 2. Install Django (`$ pip install django`) 17 | 3. Create a new project using this template 18 | 19 | ## Creating Your Project 20 | 21 | Using this template to create a new Django app is easy:: 22 | 23 | $ django-admin.py startproject --template=https://github.com/heroku/heroku-django-template/archive/master.zip --name=Procfile helloworld 24 | 25 | (If this doesn't work on windows, replace `django-admin.py` with `django-admin`) 26 | 27 | You can replace ``helloworld`` with your desired project name. 28 | 29 | ## Deployment to Heroku 30 | 31 | $ git init 32 | $ git add -A 33 | $ git commit -m "Initial commit" 34 | 35 | $ heroku create 36 | $ git push heroku master 37 | 38 | $ heroku run python manage.py migrate 39 | 40 | See also, a [ready-made application](https://github.com/heroku/python-getting-started), ready to deploy. 41 | 42 | ## Using Python 2.7? 43 | 44 | Just update `runtime.txt` to `python-2.7.13` (no trailing spaces or newlines!). 45 | 46 | 47 | ## License: MIT 48 | 49 | ## Further Reading 50 | 51 | - [Gunicorn](https://warehouse.python.org/project/gunicorn/) 52 | - [WhiteNoise](https://warehouse.python.org/project/whitenoise/) 53 | - [dj-database-url](https://warehouse.python.org/project/dj-database-url/) 54 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/__init__.py -------------------------------------------------------------------------------- /backend/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/apps/__init__.py -------------------------------------------------------------------------------- /backend/apps/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/apps/users/__init__.py -------------------------------------------------------------------------------- /backend/apps/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.1 on 2017-06-10 19:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0008_alter_user_username_max_length'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='EmailUser', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('password', models.CharField(max_length=128, verbose_name='password')), 23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 25 | ('first_name', models.CharField(max_length=30)), 26 | ('last_name', models.CharField(max_length=30)), 27 | ('email', models.EmailField(error_messages={'unique': 'That email address is already taken.'}, max_length=254, unique=True)), 28 | ('is_staff', models.BooleanField(default=False)), 29 | ('is_active', models.BooleanField(default=True)), 30 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), 31 | ('last_updated', models.DateTimeField(auto_now=True)), 32 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 33 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 34 | ], 35 | options={ 36 | 'permissions': (('view_emailuser', 'Can view email users'),), 37 | }, 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /backend/apps/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/apps/users/migrations/__init__.py -------------------------------------------------------------------------------- /backend/apps/users/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin, 4 | BaseUserManager) 5 | from django.db import models 6 | from django.utils import timezone 7 | 8 | 9 | class EmailUserManager(BaseUserManager): 10 | def _create_user(self, email, password, is_staff, is_superuser, 11 | **extra_fields): 12 | now = timezone.now() 13 | 14 | if not email: 15 | raise ValueError('The given email must be set') 16 | 17 | email = self.normalize_email(email) 18 | is_active = extra_fields.pop("is_active", True) 19 | 20 | user = self.model( 21 | email=email, 22 | is_staff=is_staff, 23 | is_active=is_active, 24 | is_superuser=is_superuser, 25 | last_login=now, 26 | date_joined=now, 27 | **extra_fields 28 | ) 29 | 30 | user.set_password(password) 31 | user.save(using=self._db) 32 | 33 | return user 34 | 35 | def create_user(self, email, password=None, **extra_fields): 36 | is_staff = extra_fields.pop("is_staff", False) 37 | 38 | return self._create_user( 39 | email, 40 | password, 41 | is_staff, 42 | False, 43 | **extra_fields 44 | ) 45 | 46 | def create_superuser(self, email, password, **extra_fields): 47 | return self._create_user( 48 | email, password, 49 | True, 50 | True, 51 | **extra_fields 52 | ) 53 | 54 | 55 | class EmailUser(AbstractBaseUser, PermissionsMixin): 56 | first_name = models.CharField(max_length=30) 57 | last_name = models.CharField(max_length=30) 58 | 59 | email = models.EmailField( 60 | max_length=254, 61 | unique=True, 62 | error_messages={ 63 | 'unique': 'That email address is already taken.' 64 | } 65 | ) 66 | 67 | is_staff = models.BooleanField(default=False) 68 | is_active = models.BooleanField(default=True) 69 | date_joined = models.DateTimeField(default=timezone.now) 70 | last_updated = models.DateTimeField(auto_now=True) 71 | objects = EmailUserManager() 72 | 73 | USERNAME_FIELD = 'email' 74 | REQUIRED_FIELDS = ['first_name', 'last_name'] 75 | 76 | class Meta: 77 | permissions = ( 78 | ('view_emailuser', 'Can view email users'), 79 | ) 80 | 81 | def __unicode__(self): 82 | return self.email 83 | 84 | def get_short_name(self): 85 | return '{first_name}'.format( 86 | first_name=self.first_name 87 | ) 88 | 89 | def get_full_name(self): 90 | return '{first_name} {last_name}'.format( 91 | first_name=self.first_name, 92 | last_name=self.last_name, 93 | ) 94 | -------------------------------------------------------------------------------- /backend/apps/users/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from rest_framework import serializers 3 | 4 | User = get_user_model() 5 | 6 | 7 | class UserSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = User 10 | fields = ( 11 | 'id', 'first_name', 'last_name', 'email', 'last_login', 12 | 'is_active', 'date_joined', 'last_updated' 13 | ) 14 | -------------------------------------------------------------------------------- /backend/apps/users/urls.py: -------------------------------------------------------------------------------- 1 | from rest_framework.routers import DefaultRouter 2 | 3 | from .views import UserViewSet 4 | 5 | router = DefaultRouter() 6 | router.register(r'users', UserViewSet) 7 | urlpatterns = router.urls 8 | -------------------------------------------------------------------------------- /backend/apps/users/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | from django.contrib.auth import get_user_model 3 | 4 | from .serializers import UserSerializer 5 | 6 | User = get_user_model() 7 | 8 | 9 | class UserViewSet(viewsets.ModelViewSet): 10 | serializer_class = UserSerializer 11 | queryset = User.objects.all() 12 | search_fields = ('first_name', 'last_name', 'email') 13 | filter_fields = ('id', 'first_name', 'last_name', 'email') 14 | -------------------------------------------------------------------------------- /backend/pagination.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import EmptyPage 2 | from rest_framework import pagination 3 | from rest_framework.response import Response 4 | 5 | 6 | class StandardPagination(pagination.PageNumberPagination): 7 | page_size = 10 8 | page_size_query_param = 'page_size' 9 | max_page_size = 50 10 | 11 | def get_paginated_response(self, data): 12 | try: 13 | previous_page_number = self.page.previous_page_number() 14 | except EmptyPage: 15 | previous_page_number = None 16 | 17 | try: 18 | next_page_number = self.page.next_page_number() 19 | except EmptyPage: 20 | next_page_number = None 21 | 22 | return Response({ 23 | 'pagination': { 24 | 'previous_page': previous_page_number, 25 | 'next_page': next_page_number, 26 | 'start_index': self.page.start_index(), 27 | 'end_index': self.page.end_index(), 28 | 'total_entries': self.page.paginator.count, 29 | 'total_pages': self.page.paginator.num_pages, 30 | 'page': self.page.number, 31 | }, 32 | 'models': data, 33 | }) 34 | -------------------------------------------------------------------------------- /backend/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import DjangoModelPermissions 2 | 3 | 4 | class DjangoModelViewPermissions(DjangoModelPermissions): 5 | """ 6 | Create our own permissions class to account for the custom "view" permission 7 | that needs to be created on any of our models. 8 | """ 9 | 10 | perms_map = { 11 | 'GET': ['%(app_label)s.view_%(model_name)s'], 12 | 'OPTIONS': ['%(app_label)s.view_%(model_name)s'], 13 | 'HEAD': ['%(app_label)s.view_%(model_name)s'], 14 | 'POST': ['%(app_label)s.view_%(model_name)s'], 15 | 'PUT': ['%(app_label)s.change_%(model_name)s'], 16 | 'PATCH': ['%(app_label)s.change_%(model_name)s'], 17 | 'DELETE': ['%(app_label)s.delete_%(model_name)s'], 18 | } 19 | -------------------------------------------------------------------------------- /backend/settings/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .local import * 3 | except: 4 | from .production import * 5 | -------------------------------------------------------------------------------- /backend/settings/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project on Heroku. For more info, see: 3 | https://github.com/heroku/heroku-django-template 4 | 5 | For more information on this file, see 6 | https://docs.djangoproject.com/en/1.10/topics/settings/ 7 | 8 | For the full list of settings and their values, see 9 | https://docs.djangoproject.com/en/1.10/ref/settings/ 10 | """ 11 | 12 | import os 13 | from sys import path 14 | from os.path import join 15 | import dotenv 16 | from django.core.urlresolvers import reverse_lazy 17 | 18 | dotenv.load() 19 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 20 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 21 | PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 22 | path.append(join(BASE_DIR, 'apps')) 23 | 24 | 25 | # Quick-start development settings - unsuitable for production 26 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 27 | 28 | DEBUG = False 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = [ 33 | 'material', 34 | 'material.admin', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | # Disable Django's own staticfiles handling in favour of WhiteNoise, for 41 | # greater consistency between gunicorn and `./manage.py runserver`. See: 42 | # http://whitenoise.evans.io/en/stable/django.html#using-whitenoise-in-development 43 | 'whitenoise.runserver_nostatic', 44 | 'django.contrib.staticfiles', 45 | "django_extensions", 46 | "rest_framework" 47 | ] 48 | 49 | LOCAL_APPS = [ 50 | "users" 51 | ] 52 | 53 | INSTALLED_APPS += LOCAL_APPS 54 | 55 | MIDDLEWARE = [ 56 | 'django.middleware.security.SecurityMiddleware', 57 | 'whitenoise.middleware.WhiteNoiseMiddleware', 58 | 'django.contrib.sessions.middleware.SessionMiddleware', 59 | 'django.middleware.common.CommonMiddleware', 60 | 'django.middleware.csrf.CsrfViewMiddleware', 61 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 62 | 'django.contrib.messages.middleware.MessageMiddleware', 63 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 64 | ] 65 | 66 | ROOT_URLCONF = 'backend.urls' 67 | 68 | TEMPLATES = [ 69 | { 70 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 71 | 'DIRS': [ 72 | os.path.join(BASE_DIR, "templates") 73 | ], 74 | 'APP_DIRS': True, 75 | 'OPTIONS': { 76 | 'context_processors': [ 77 | 'django.template.context_processors.debug', 78 | 'django.template.context_processors.request', 79 | 'django.contrib.auth.context_processors.auth', 80 | 'django.contrib.messages.context_processors.messages', 81 | ], 82 | 'debug': DEBUG, 83 | }, 84 | }, 85 | ] 86 | 87 | WSGI_APPLICATION = 'backend.wsgi.application' 88 | 89 | 90 | # Database 91 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 92 | 93 | DATABASES = { 94 | 'default': { 95 | 'ENGINE': 'django.db.backends.sqlite3', 96 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 97 | } 98 | } 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 106 | }, 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 109 | }, 110 | { 111 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 112 | }, 113 | ] 114 | 115 | # Internationalization 116 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 117 | 118 | LANGUAGE_CODE = 'en-us' 119 | TIME_ZONE = 'UTC' 120 | USE_I18N = True 121 | USE_L10N = True 122 | USE_TZ = True 123 | 124 | # Honor the 'X-Forwarded-Proto' header for request.is_secure() 125 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 126 | 127 | # Allow all host headers 128 | ALLOWED_HOSTS = ['*'] 129 | 130 | # Static files (CSS, JavaScript, Images) 131 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 132 | 133 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 134 | STATIC_URL = '/static/' 135 | # Extra places for collectstatic to find static files. 136 | STATICFILES_DIRS = [ 137 | os.path.join(BASE_DIR, 'static'), 138 | ] 139 | 140 | # Simplified static file serving. 141 | # https://warehouse.python.org/project/whitenoise/ 142 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 143 | 144 | LOGIN_REDIRECT_URL = reverse_lazy("app") 145 | LOGIN_URL = reverse_lazy("login") 146 | 147 | REST_FRAMEWORK = { 148 | "DEFAULT_RENDERER_CLASSES": ( 149 | "rest_framework.renderers.BrowsableAPIRenderer", 150 | "rest_framework.renderers.JSONRenderer", 151 | ), 152 | "DEFAULT_PARSER_CLASSES": ( 153 | "rest_framework.parsers.JSONParser", 154 | ), 155 | "DEFAULT_PERMISSION_CLASSES": ( 156 | "rest_framework.permissions.IsAuthenticated", 157 | "backend.permissions.DjangoModelViewPermissions", 158 | ), 159 | "DEFAULT_AUTHENTICATION_CLASSES": ( 160 | "rest_framework.authentication.SessionAuthentication", 161 | ), 162 | "DEFAULT_PAGINATION_CLASS": "backend.pagination.StandardPagination", 163 | "DEFAULT_FILTER_BACKENDS": ( 164 | "rest_framework.filters.SearchFilter", 165 | "rest_framework.filters.DjangoFilterBackend", 166 | ), 167 | } 168 | 169 | AUTH_USER_MODEL = 'users.EmailUser' -------------------------------------------------------------------------------- /backend/settings/devl.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | 3 | # SECURITY WARNING: keep the secret key used in production secret! 4 | SECRET_KEY = 't9uk7z8vrm@43978@*v42dvr**9+qe&+!s!51u!777#o66lbx4' 5 | 6 | # SECURITY WARNING: don't run with debug turned on in production! 7 | DEBUG = True 8 | 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': 'django.db.backends.postgresql', 12 | 'USER': dotenv.get('DB_USER'), 13 | 'NAME': dotenv.get('DB_NAME'), 14 | 'PASSWORD': dotenv.get('DB_PASSWORD'), 15 | 'HOST': dotenv.get('DB_HOST'), 16 | 'PORT': dotenv.get('DB_PORT'), 17 | } 18 | } 19 | 20 | INTERNAL_IPS = ['192.168.56.1'] 21 | 22 | INSTALLED_APPS += ( 23 | 'autofixture', 24 | ) 25 | 26 | STATICFILES_DIRS.append( 27 | os.path.join(BASE_DIR, os.pardir, 'frontend', 'build'), 28 | ) 29 | -------------------------------------------------------------------------------- /backend/settings/production.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | import dj_database_url 3 | 4 | # SECURITY WARNING: keep the secret key used in production secret! 5 | SECRET_KEY = dotenv.get('SECRET_KEY') 6 | 7 | # SECURITY WARNING: don't run with debug turned on in production! 8 | DEBUG = False 9 | 10 | # Update database configuration with $DATABASE_URL. 11 | db_from_env = dj_database_url.config(conn_max_age=500) 12 | DATABASES['default'].update(db_from_env) 13 | 14 | STATICFILES_DIRS.append( 15 | os.path.join(PROJECT_ROOT, 'frontend', 'build'), 16 | ) 17 | 18 | ALLOWED_HOSTS = [ 19 | '*' 20 | ] -------------------------------------------------------------------------------- /backend/static/adminlte/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/avatar.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/avatar04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/avatar04.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/avatar2.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/avatar3.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/avatar5.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/boxed-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/boxed-bg.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/boxed-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/boxed-bg.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/credit/american-express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/credit/american-express.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/credit/cirrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/credit/cirrus.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/credit/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/credit/mastercard.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/credit/mestro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/credit/mestro.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/credit/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/credit/paypal.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/credit/paypal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/credit/paypal2.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/credit/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/credit/visa.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/default-50x50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/default-50x50.gif -------------------------------------------------------------------------------- /backend/static/adminlte/img/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/icons.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/photo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/photo1.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/photo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/photo2.png -------------------------------------------------------------------------------- /backend/static/adminlte/img/photo3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/photo3.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/photo4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/photo4.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user1-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user1-128x128.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user2-160x160.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user2-160x160.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user3-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user3-128x128.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user4-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user4-128x128.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user5-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user5-128x128.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user6-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user6-128x128.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user7-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user7-128x128.jpg -------------------------------------------------------------------------------- /backend/static/adminlte/img/user8-128x128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/adminlte/img/user8-128x128.jpg -------------------------------------------------------------------------------- /backend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/favicon.ico -------------------------------------------------------------------------------- /backend/static/humans.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/backend/static/humans.txt -------------------------------------------------------------------------------- /backend/templates/backend/app.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | Django & React Starter 8 | 9 | 10 | 14 | 15 | 16 | 17 |
18 | 19 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /backend/templates/backend/test.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | Django & React Starter 8 | 9 | 13 | 14 | 15 |

hello test

16 | 17 | 18 | -------------------------------------------------------------------------------- /backend/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | {% block head %} 5 | 6 | 7 | 8 | Django & React Starter 9 | 10 | 11 | 12 | 16 | 17 | {% endblock head %} 18 | {% block body %} 19 | 20 | {% block wrapper %} 21 |
22 | {% block main_header %} 23 | {% include "adminlte/main_header.html" %} 24 | {% endblock main_header %} 25 | 26 | {% block main_sidebar %} 27 | {% include "adminlte/main_sidebar.html" %} 28 | {% endblock main_sidebar %} 29 | 30 | {% block content_wrapper %} 31 |
32 | {% block content %} 33 | {% endblock content %} 34 |
35 | {% endblock content_wrapper %} 36 | 37 | {% block footer %} 38 | {% include "adminlte/footer.html" %} 39 | {% endblock footer %} 40 |
41 | {% endblock wrapper %} 42 | 43 | {% endblock body %} 44 | 45 | -------------------------------------------------------------------------------- /backend/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "registration/base.html" %} 2 | {% load static %} 3 | 4 | {% block body %} 5 | 6 |
7 | 10 |
11 |
12 | {% csrf_token %} 13 |
14 | 16 | 17 |
18 |
19 | 21 | 22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 | I forgot my password
31 |
32 |
33 | 34 | {% endblock body %} 35 | -------------------------------------------------------------------------------- /backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include 2 | from django.contrib.auth.views import login, logout_then_login 3 | from django.contrib import admin 4 | 5 | from .views import app, index 6 | 7 | urlpatterns = [ 8 | url(r'^admin/', admin.site.urls), 9 | url(r'^api/', include('users.urls')), 10 | url(r'^app/', app, name='app'), 11 | url('^auth/login/$', login, name='login'), 12 | url('^auth/logout/$', logout_then_login, name='logout'), 13 | url('^$', index, name='index'), 14 | ] 15 | -------------------------------------------------------------------------------- /backend/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.contrib.auth.decorators import login_required 4 | from django.conf import settings 5 | from django.core.urlresolvers import reverse 6 | from django.shortcuts import render, redirect 7 | 8 | 9 | def index(request): 10 | if request.user.is_authenticated(): 11 | return redirect(reverse("app")) 12 | else: 13 | return redirect(settings.LOGIN_REDIRECT_URL) 14 | 15 | 16 | @login_required 17 | def app(request): 18 | context = { 19 | 'permissions': json.dumps(list(request.user.get_all_permissions())) 20 | } 21 | template = 'backend/app.html' 22 | return render(request, template, context) 23 | -------------------------------------------------------------------------------- /backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/db.sqlite3 -------------------------------------------------------------------------------- /dev.txt: -------------------------------------------------------------------------------- 1 | -r ./requirements.txt 2 | django-autofixture==0.12.0 3 | flake8==2.5.4 4 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"], 3 | "env": { 4 | "optional": ["es7.classProperties"], 5 | "development": { 6 | "plugins": [ 7 | "transform-decorators-legacy" 8 | ] 9 | }, 10 | "production": { 11 | "plugins": [ 12 | "transform-decorators-legacy" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": { 12 | "comma-dangle": 2, 13 | "quotes": [2, "double"], 14 | "space-infix-ops": 2, 15 | "strict": 2, 16 | "babel/generator-star-spacing": 1, 17 | "babel/object-shorthand": 1, 18 | "babel/arrow-parens": 1, 19 | "babel/no-await-in-loop": 1, 20 | "react/jsx-uses-react": 2, 21 | "react/jsx-uses-vars": 2, 22 | "react/react-in-jsx-scope": 2, 23 | "semi": [2, "always"], 24 | "new-cap": [2, { 25 | "capIsNewExceptions": ["List", "Map", "Record", "Set", "OrderedSet"] 26 | }], 27 | "no-unused-vars": 2 28 | }, 29 | "plugins": [ 30 | "babel", 31 | "react" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | dist/* -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React Based SPA 2 | 3 | ## Primary Modules 4 | 1. [react](https://facebook.github.io/react/) 5 | 1. [AdminLTE](https://almsaeedstudio.com/themes/AdminLTE/index2.html) Bootstrap based theme 6 | 1. [immutable.js](https://facebook.github.io/immutable-js/) 7 | 1. [webpack](https://webpack.github.io/) 8 | 1. [react-router](https://github.com/reactjs/react-router) 9 | 1. [redux](https://github.com/reactjs/redux) 10 | 1. [react-bootstrap](https://react-bootstrap.github.io/) 11 | 1. [font awesome](https://fortawesome.github.io/Font-Awesome/) 12 | 13 | ## Prerequisites 14 | 1. npm 15 | 16 | ## Installation 17 | 18 | ``` 19 | cd frontend 20 | npm install 21 | npm start 22 | ``` 23 | -------------------------------------------------------------------------------- /frontend/build/admin-lte/dist/img/boxed-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/admin-lte/dist/img/boxed-bg.jpg -------------------------------------------------------------------------------- /frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /frontend/build/emptyFile.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/emptyFile.txt -------------------------------------------------------------------------------- /frontend/build/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /frontend/build/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /frontend/build/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /frontend/build/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundayguru/django-react-heroku/4b0abaddbe0aa74435df19f2a21a4a647050b9b5/frontend/build/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "your_project", 3 | "version": "0.0.1", 4 | "description": "A Django & React Template", 5 | "scripts": { 6 | "clean": "rimraf dist", 7 | "build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js", 8 | "build": "npm run clean && npm run build:webpack", 9 | "start": "webpack -c --progress -w --config webpack.config.dev.js", 10 | "lint": "eslint src/" 11 | }, 12 | "author": "Your Name", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "babel-core": "^6.7.4", 16 | "babel-eslint": "^5.0.0", 17 | "babel-loader": "^6.2.4", 18 | "babel-plugin-react-transform": "^2.0.0-beta1", 19 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 20 | "babel-preset-es2015": "^6.6.0", 21 | "babel-preset-react": "^6.5.0", 22 | "babel-preset-stage-0": "^6.5.0", 23 | "bootstrap": "^3.3.6", 24 | "css-loader": "^0.23.1", 25 | "eslint": "^1.10.3", 26 | "eslint-plugin-babel": "^3.1.0", 27 | "eslint-plugin-react": "^3.11.3", 28 | "expose-loader": "^0.7.1", 29 | "extract-text-webpack-plugin": "^1.0.1", 30 | "file-loader": "^0.8.5", 31 | "font-awesome": "^4.5.0", 32 | "less": "^2.6.0", 33 | "less-loader": "^2.2.2", 34 | "rimraf": "^2.4.3", 35 | "style-loader": "^0.13.0", 36 | "url-loader": "^0.5.7", 37 | "webpack": "^1.12.14" 38 | }, 39 | "dependencies": { 40 | "admin-lte": "^2.3.2", 41 | "classnames": "2.2.3", 42 | "history": "2.0.1", 43 | "immutable": "^3.7.6", 44 | "lodash": "3.10.1", 45 | "moment": "^2.11.2", 46 | "nprogress": "^0.2.0", 47 | "react": "0.14.7", 48 | "react-bootstrap": "^0.28.3", 49 | "react-dom": "0.14.7", 50 | "react-redux": "^4.4.1", 51 | "react-router": "^2.0.1", 52 | "redux": "^3.3.1", 53 | "redux-logger": "^2.6.1", 54 | "redux-thunk": "^2.0.1", 55 | "reselect": "^2.2.1", 56 | "superagent": "^1.8.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Box/Body.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Body extends React.Component { 5 | render() { 6 | return ( 7 |
8 | {this.props.children} 9 |
10 | ); 11 | } 12 | } 13 | 14 | export default Body; 15 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Box/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classnames from "classnames"; 3 | 4 | 5 | class Header extends React.Component { 6 | render() { 7 | const props = this.props; 8 | 9 | const classNames = classnames({ 10 | "box-header": true, 11 | "with-border": props.withBorder 12 | }); 13 | 14 | return ( 15 |
16 | {props.children} 17 |
18 | ); 19 | } 20 | } 21 | 22 | Header.defaultProps = { 23 | withBorder: true 24 | }; 25 | 26 | export default Header; 27 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Box/Title.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class HeaderTitle extends React.Component { 5 | render() { 6 | const props = this.props; 7 | 8 | return ( 9 |

{props.children}

10 | ); 11 | } 12 | } 13 | 14 | export default HeaderTitle; 15 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Box/Tools.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classnames from "classnames"; 3 | 4 | 5 | class Tools extends React.Component { 6 | render() { 7 | const props = this.props; 8 | 9 | const classNames = classnames({ 10 | "box-tools": true, 11 | "pull-right": props.pullRight 12 | }); 13 | 14 | return ( 15 |
16 | {props.children} 17 |
18 | ); 19 | } 20 | } 21 | 22 | Tools.defaultProps = { 23 | pullRight: true 24 | }; 25 | 26 | export default Tools; 27 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Box/Wrapper.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Wrapper extends React.Component { 5 | render() { 6 | return ( 7 |
8 | {this.props.children} 9 |
10 | ); 11 | } 12 | } 13 | 14 | export default Wrapper; 15 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Box/index.js: -------------------------------------------------------------------------------- 1 | import Body from "./Body"; 2 | import Header from "./Header"; 3 | import Title from "./Title"; 4 | import Tools from "./Tools"; 5 | import Wrapper from "./Wrapper"; 6 | 7 | export { 8 | Body, 9 | Header, 10 | Title, 11 | Tools, 12 | Wrapper 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Content.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Content extends React.Component { 5 | render() { 6 | const {children} = this.props; 7 | 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | } 14 | } 15 | 16 | export default Content; 17 | -------------------------------------------------------------------------------- /frontend/src/adminlte/ContentHeader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class ContentHeader extends React.Component { 5 | render() { 6 | const {children} = this.props; 7 | 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | } 14 | } 15 | 16 | export default ContentHeader; 17 | -------------------------------------------------------------------------------- /frontend/src/adminlte/ContentWrapper.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {connect} from "react-redux"; 3 | 4 | import {contentWrapperMinHeight} from "./selectors"; 5 | 6 | 7 | class ContentWrapper extends React.Component { 8 | render() { 9 | const {children, contentWrapperMinHeight} = this.props; 10 | 11 | return ( 12 |
16 | {children} 17 |
18 | ); 19 | } 20 | } 21 | 22 | export default connect(contentWrapperMinHeight)(ContentWrapper); 23 | -------------------------------------------------------------------------------- /frontend/src/adminlte/ControlSidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classnames from "classnames"; 3 | 4 | 5 | class ControlSidebar extends React.Component { 6 | render() { 7 | const {adminlte, children} = this.props; 8 | const className = classnames(adminlte.controlSidebar.get("classNames").toJS()); 9 | 10 | return ( 11 |
12 | 15 |
18 |
19 |
20 | ); 21 | } 22 | } 23 | 24 | export default ControlSidebar; 25 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/ControlSidebarToggle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class ControlSidebarToggle extends React.Component { 5 | render() { 6 | const {actions} = this.props; 7 | 8 | return ( 9 | 15 | ); 16 | } 17 | } 18 | 19 | export default ControlSidebarToggle; 20 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/Logo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Logo extends React.Component { 5 | render() { 6 | return ( 7 | 8 | ALT 9 | AdminLTE 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default Logo; 16 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/MainSidebarToggle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class MainSidebarToggle extends React.Component { 5 | render() { 6 | const {actions} = this.props; 7 | 8 | return ( 9 | 12 | ); 13 | } 14 | } 15 | 16 | export default MainSidebarToggle; 17 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/Menu.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Menu extends React.Component { 5 | render() { 6 | const {children} = this.props; 7 | 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | } 14 | } 15 | 16 | export default Menu; 17 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/Nav.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Nav extends React.Component { 5 | render() { 6 | const {children} = this.props; 7 | 8 | return ( 9 | 12 | ); 13 | } 14 | } 15 | 16 | export default Nav; 17 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Navbar extends React.Component { 5 | render() { 6 | const {children} = this.props; 7 | 8 | return ( 9 | 12 | ); 13 | } 14 | } 15 | 16 | export default Navbar; 17 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/UserMenu.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames"; 3 | 4 | 5 | class UserMenu extends React.Component { 6 | render() { 7 | const {actions, adminlte} = this.props; 8 | const userPanel = adminlte.getIn(["mainHeader", "userPanel"]); 9 | const staticRoot = window.django.urls.staticRoot; 10 | 11 | const classes = classNames({ 12 | "dropdown": true, 13 | "user": true, 14 | "user-menu": true, 15 | "open": ! userPanel.get("collapsed") 16 | }); 17 | 18 | return ( 19 |
  • 20 | 24 | User Image 28 | {window.django.user.full_name} 29 | 30 | 31 | 69 |
  • 70 | ); 71 | } 72 | } 73 | 74 | export default UserMenu; 75 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/Wrapper.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Wrapper extends React.Component { 5 | render() { 6 | const {children} = this.props; 7 | 8 | return ( 9 |
    10 | {children} 11 |
    12 | ); 13 | } 14 | } 15 | 16 | export default Wrapper; 17 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainHeader/index.js: -------------------------------------------------------------------------------- 1 | import ControlSidebarToggle from "./ControlSidebarToggle"; 2 | import Logo from "./Logo"; 3 | import MainSidebarToggle from "./MainSidebarToggle"; 4 | import Nav from "./Nav"; 5 | import Navbar from "./Navbar"; 6 | import Menu from "./Menu"; 7 | import UserMenu from "./UserMenu"; 8 | import Wrapper from "./Wrapper"; 9 | 10 | export { 11 | ControlSidebarToggle, 12 | Logo, 13 | MainSidebarToggle, 14 | Nav, 15 | Navbar, 16 | Menu, 17 | UserMenu, 18 | Wrapper 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainSidebar/Form.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | function SidebarForm() { 5 | return ( 6 |
    7 |
    8 | 9 | 10 | 13 | 14 |
    15 |
    16 | ); 17 | } 18 | 19 | export default SidebarForm; 20 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainSidebar/Menu.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import _ from "lodash"; 3 | import {Link} from "react-router"; 4 | 5 | 6 | const allLinks = [ 7 | { 8 | permission: "users.view_emailuser", 9 | text: "Users", 10 | to: "/admin/users/", 11 | icon: "fa fa-users" 12 | } 13 | ]; 14 | 15 | const userLinks = _.filter(allLinks, (link) => { 16 | return window.django.user.permissions.has(link.permission); 17 | }); 18 | 19 | 20 | export default class Menu extends React.Component { 21 | render() { 22 | return ( 23 |
    24 | 35 |
    36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainSidebar/UserPanel.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | export default function UserPanel() { 5 | return ( 6 |
    7 |
    8 | User Image 13 |
    14 |
    15 |

    Alexander Pierce

    16 | Online 17 |
    18 |
    19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainSidebar/Wrapper.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | function Wrapper(props) { 5 | return ( 6 | 9 | ); 10 | } 11 | 12 | export default Wrapper; 13 | -------------------------------------------------------------------------------- /frontend/src/adminlte/MainSidebar/index.js: -------------------------------------------------------------------------------- 1 | import Form from "./Form"; 2 | import Menu from "./Menu"; 3 | import UserPanel from "./UserPanel"; 4 | import Wrapper from "./Wrapper"; 5 | 6 | export { 7 | Form, 8 | Menu, 9 | UserPanel, 10 | Wrapper 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/src/adminlte/Wrapper.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | export default class Wrapper extends React.Component { 5 | render() { 6 | const {children} = this.props; 7 | 8 | return ( 9 |
    10 | {children} 11 |
    12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/adminlte/actions.js: -------------------------------------------------------------------------------- 1 | import constants from "./constants"; 2 | 3 | 4 | export function controlSidebarToggle() { 5 | // Because AdminLTE applies classes on and our React application 6 | // doesn"t manage we have to manipulate it here 7 | document.body.classList.toggle("control-sidebar-open"); 8 | 9 | return { 10 | type: constants.CONTROL_SIDEBAR_TOGGLE 11 | }; 12 | } 13 | 14 | export function mainHeaderUserMenuToggle() { 15 | return { 16 | type: constants.MAIN_HEADER_USER_PANEL_TOGGLE 17 | }; 18 | } 19 | 20 | export function mainSidebarToggle() { 21 | // Because AdminLTE applies classes on and our React application 22 | // doesn"t manage we have to manipulate it here 23 | document.body.classList.toggle("sidebar-collapse"); 24 | document.body.classList.toggle("sidebar-open"); 25 | 26 | return { 27 | type: constants.MAIN_SIDEBAR_TOGGLE 28 | }; 29 | } 30 | 31 | export default { 32 | controlSidebarToggle, 33 | mainHeaderUserMenuToggle, 34 | mainSidebarToggle 35 | }; 36 | -------------------------------------------------------------------------------- /frontend/src/adminlte/constants.js: -------------------------------------------------------------------------------- 1 | export const MAIN_HEADER_USER_PANEL_TOGGLE = "MAIN_HEADER_USER_PANEL_TOGGLE"; 2 | export const CONTROL_SIDEBAR_TOGGLE = "CONTROL_SIDEBAR_TOGGLE"; 3 | export const MAIN_SIDEBAR_TOGGLE = "MAIN_SIDEBAR_TOGGLE"; 4 | 5 | export default { 6 | CONTROL_SIDEBAR_TOGGLE, 7 | MAIN_SIDEBAR_TOGGLE, 8 | MAIN_HEADER_USER_PANEL_TOGGLE 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/adminlte/index.js: -------------------------------------------------------------------------------- 1 | import * as Box from "./Box"; 2 | import Content from "./Content"; 3 | import ContentHeader from "./ContentHeader"; 4 | import ContentWrapper from "./ContentWrapper"; 5 | import ControlSidebar from "./ControlSidebar"; 6 | import * as MainSidebar from "./MainSidebar"; 7 | import * as MainHeader from "./MainHeader"; 8 | import Wrapper from "./Wrapper"; 9 | 10 | 11 | export { 12 | Box, 13 | Content, 14 | ContentHeader, 15 | ContentWrapper, 16 | ControlSidebar, 17 | MainHeader, 18 | MainSidebar, 19 | Wrapper 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/src/adminlte/model.js: -------------------------------------------------------------------------------- 1 | import {List, Map, Record} from "immutable"; 2 | 3 | 4 | class AdminLTE extends Record({ 5 | body: Map({ 6 | classNames: List([ 7 | "skin-purple", 8 | "sidebar-mini", 9 | "sidebar-collapse" 10 | ]) 11 | }), 12 | controlSidebar: Map({ 13 | classNames: Map({ 14 | "control-sidebar": true, 15 | "control-sidebar-dark": true, 16 | "control-sidebar-open": false 17 | }) 18 | }), 19 | mainFooter: Map({ 20 | height: 51 21 | }), 22 | mainHeader: Map({ 23 | height: 50, 24 | userPanel: Map({ 25 | collapsed: true 26 | }) 27 | }), 28 | mainSidebar: Map({ 29 | collapsed: document.body.classList.contains("sidebar-collapse") 30 | }) 31 | }){} 32 | 33 | export { 34 | AdminLTE 35 | }; 36 | -------------------------------------------------------------------------------- /frontend/src/adminlte/reducers.js: -------------------------------------------------------------------------------- 1 | import constants from "./constants"; 2 | import {AdminLTE} from "./model"; 3 | 4 | const adminlte = new AdminLTE(); 5 | 6 | function reducer(state = adminlte, action) { 7 | switch (action.type) { 8 | case constants.CONTROL_SIDEBAR_TOGGLE: 9 | return state.setIn( 10 | ["controlSidebar", "classNames", "control-sidebar-open"], 11 | ! state.getIn(["controlSidebar", "classNames", "control-sidebar-open"]) 12 | ); 13 | 14 | case constants.MAIN_HEADER_USER_PANEL_TOGGLE: 15 | return state.setIn( 16 | ["mainHeader", "userPanel", "collapsed"], 17 | ! state.getIn(["mainHeader", "userPanel", "collapsed"]) 18 | ); 19 | 20 | case constants.MAIN_SIDEBAR_TOGGLE: 21 | return state.setIn( 22 | ["mainSidebar", "collapsed"], 23 | ! state.getIn(["mainSidebar", "collapsed"]) 24 | ); 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | export default reducer; 32 | -------------------------------------------------------------------------------- /frontend/src/adminlte/selectors.js: -------------------------------------------------------------------------------- 1 | import {createSelector} from "reselect"; 2 | 3 | import viewportDimensions from "app/utils/viewportDimensions"; 4 | 5 | const adminlteSelector = (state, props) => { 6 | return props.adminlte; 7 | }; 8 | 9 | const contentWrapperMinHeight = createSelector( 10 | adminlteSelector, 11 | (adminlte) => { 12 | const dimensions = viewportDimensions(); 13 | const mainFooter = adminlte.mainFooter; 14 | const mainHeader = adminlte.mainHeader; 15 | 16 | const minHeight = dimensions.height - ( 17 | mainHeader.get("height") + mainFooter.get("height") 18 | ); 19 | 20 | return { 21 | contentWrapperMinHeight: minHeight 22 | }; 23 | } 24 | ); 25 | 26 | export { 27 | contentWrapperMinHeight 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/src/app/Root.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Provider} from "react-redux"; 3 | import {Router} from "react-router"; 4 | 5 | import stylesheets from "app/stylesheets/index.less"; //eslint-disable-line no-unused-vars 6 | import configureStore from "app/configureStore"; 7 | import history from "app/history"; 8 | import urls from "app/urls"; 9 | 10 | const store = configureStore(); 11 | 12 | 13 | class Root extends React.Component { 14 | render() { 15 | return ( 16 | 17 | 18 | {urls} 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | export default Root; 26 | -------------------------------------------------------------------------------- /frontend/src/app/actions/alerts.js: -------------------------------------------------------------------------------- 1 | import * as alerts from "app/constants/alerts"; 2 | import * as http from "app/constants/http"; 3 | import EmptyComponent from "app/components/EmptyComponent"; 4 | import InternalServerError from "app/components/alerts/InternalServerError"; 5 | import SessionExpired from "app/components/alerts/SessionExpired"; 6 | 7 | 8 | function httpStatusCodeComponentLookup(statusCode) { 9 | switch (statusCode) { 10 | case http.HTTP_403_FORBIDDEN: 11 | return SessionExpired; 12 | case http.HTTP_500_INTERNAL_SERVER_ERROR: 13 | return InternalServerError; 14 | default: 15 | return EmptyComponent; 16 | } 17 | } 18 | 19 | function addHttpStatusCodeAlert(statusCode) { 20 | const component = httpStatusCodeComponentLookup(statusCode); 21 | 22 | return { 23 | type: alerts.ADD_ALERT, 24 | component 25 | }; 26 | } 27 | 28 | export { 29 | addHttpStatusCodeAlert 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/app/actions/collection.js: -------------------------------------------------------------------------------- 1 | import {List} from "immutable"; 2 | 3 | import http from "app/utils/http"; 4 | import {addHttpStatusCodeAlert} from "app/actions/alerts"; 5 | 6 | 7 | function collectionIsLoading(collection) { 8 | const constants = collection.get("constants"); 9 | return { 10 | type: constants.COLLECTION_IS_LOADING 11 | }; 12 | } 13 | 14 | function receivedCollection(collection, data) { 15 | const constants = collection.get("constants"); 16 | return { 17 | models: data.models, 18 | pagination: data.pagination, 19 | type: constants.RECEIVED_COLLECTION 20 | }; 21 | } 22 | 23 | function receivedModel(model, data) { 24 | const constants = model.get("constants"); 25 | return { 26 | model: data, 27 | type: constants.RECEIVED_MODEL 28 | }; 29 | } 30 | 31 | function removedModel(model) { 32 | const constants = model.get("constants"); 33 | return { 34 | type: constants.REMOVED_MODEL, 35 | model 36 | }; 37 | } 38 | 39 | export function saveModel({model, successCb = List(), errorCb = List(), changeSet}) { 40 | return (dispatch) => { 41 | successCb = successCb.concat( 42 | (response) => dispatch(receivedModel(model, response.body)) 43 | ); 44 | 45 | errorCb = errorCb.concat( 46 | (response) => dispatch(addHttpStatusCodeAlert(response.statusCode)), 47 | ); 48 | 49 | return http.put(model.apiUrl(), changeSet.toJS(), successCb, errorCb); 50 | }; 51 | } 52 | 53 | export function createModel({collection, successCb = List(), errorCb = List(), changeSet}) { 54 | return (dispatch) => { 55 | successCb = successCb.concat( 56 | (response) => { 57 | dispatch(receivedModel(collection, response.body)); 58 | } 59 | ); 60 | 61 | errorCb = errorCb.concat( 62 | (response) => dispatch(addHttpStatusCodeAlert(response.statusCode)), 63 | ); 64 | 65 | return http.post(collection.apiUrl, changeSet.toJS(), successCb, errorCb); 66 | }; 67 | } 68 | 69 | export function fetchModel({model, successCb = List(), errorCb = List()}) { 70 | return (dispatch) => { 71 | successCb = successCb.concat( 72 | (response) => dispatch(receivedModel(model, response.body)) 73 | ); 74 | 75 | errorCb = errorCb.concat( 76 | (response) => dispatch(addHttpStatusCodeAlert(response.statusCode)) 77 | ); 78 | 79 | return http.get(model.apiUrl(), {}, successCb, errorCb); 80 | }; 81 | } 82 | 83 | export function deleteModel({model, successCb = List(), errorCb = List()}) { 84 | return (dispatch) => { 85 | successCb = successCb.concat( 86 | () => dispatch(removedModel(model)) 87 | ); 88 | 89 | errorCb = errorCb.concat( 90 | (response) => dispatch(addHttpStatusCodeAlert(response.statusCode)) 91 | ); 92 | 93 | return http.del(model.apiUrl(), successCb, errorCb); 94 | }; 95 | } 96 | 97 | export function fetchCollectionIfEmpty({collection, query}) { 98 | return (dispatch) => { 99 | const models = collection.get("models"); 100 | if (models.size === 0) { 101 | return dispatch(fetchCollection({collection, query})); 102 | } 103 | }; 104 | } 105 | 106 | export function fetchCollection({collection, query, successCb = List(), errorCb = List()}) { 107 | return (dispatch) => { 108 | const currentQuery = collection.get("query"); 109 | 110 | if ( ! currentQuery.equals(query)) { 111 | dispatch(updateCollectionQuery({collection, query})); 112 | } 113 | 114 | dispatch(collectionIsLoading(collection)); 115 | 116 | successCb = successCb.concat( 117 | (response) => dispatch(receivedCollection(collection, response.body)) 118 | ); 119 | 120 | errorCb.concat( 121 | (response) => dispatch(addHttpStatusCodeAlert(response.statusCode)) 122 | ); 123 | 124 | return http.get(collection.get("apiUrl"), query.toJS(), successCb, errorCb); 125 | }; 126 | } 127 | 128 | export function updateCollectionQuery({collection, query}) { 129 | const constants = collection.get("constants"); 130 | return { 131 | query, 132 | type: constants.UPDATE_COLLECTION_QUERY 133 | }; 134 | } 135 | 136 | export default { 137 | createModel, 138 | deleteModel, 139 | fetchCollection, 140 | fetchCollectionIfEmpty, 141 | fetchModel, 142 | saveModel, 143 | updateCollectionQuery 144 | }; 145 | -------------------------------------------------------------------------------- /frontend/src/app/components/DeleteButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Button, Modal} from "react-bootstrap"; 3 | 4 | 5 | class DeleteButton extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = {show: false}; 9 | } 10 | 11 | showModal = () => { 12 | this.setState({show: true}); 13 | } 14 | 15 | hideModal = () => { 16 | this.setState({show: false}); 17 | } 18 | 19 | handleDelete = () => { 20 | const {actions, collection, model} = this.props; 21 | const {router} = this.context; 22 | actions.deleteModel({model}); 23 | router.push(collection.appUrl()); 24 | } 25 | 26 | render() { 27 | const {permission} = this.props; 28 | 29 | if (window.django.user.permissions.has(permission)) { 30 | const {model} = this.props; 31 | 32 | return( 33 | 34 | Delete 35 | 41 | 42 | Confirm Deletion 43 | 44 | 45 | Are you sure you want to delete {model.toString()}? 46 | This action cannot be undone. 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | ); 58 | } else { 59 | return null; 60 | } 61 | } 62 | } 63 | 64 | DeleteButton.contextTypes = { 65 | router: React.PropTypes.object 66 | }; 67 | 68 | export default DeleteButton; 69 | -------------------------------------------------------------------------------- /frontend/src/app/components/EmptyComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class EmptyComponent extends React.Component { 5 | render() { 6 | return null; 7 | } 8 | } 9 | 10 | export default EmptyComponent; 11 | -------------------------------------------------------------------------------- /frontend/src/app/components/LinkedListGroup.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router"; 3 | 4 | 5 | class LinkedListGroup extends React.Component { 6 | render() { 7 | const {collection} = this.props; 8 | 9 | return ( 10 |
    11 | {collection.models.toList().map((model, key) => 12 | 18 | {model.toString()} 19 | 20 | 21 | 22 | 23 | )} 24 | {collection.models.size === 0 25 | &&
    No items found.
    } 26 |
    27 | ); 28 | } 29 | } 30 | 31 | export default LinkedListGroup; 32 | -------------------------------------------------------------------------------- /frontend/src/app/components/Pagination.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Col, Pagination, Row} from "react-bootstrap"; 3 | 4 | import PaginationInfo from "app/components/PaginationInfo"; 5 | 6 | 7 | class GenericPagination extends React.Component { 8 | handleSelect = (evnt, selectedEvent) => { 9 | const {actions, collection, params} = this.props; 10 | const {router} = this.context; 11 | const query = collection.get("query").set("page", selectedEvent.eventKey); 12 | 13 | actions.fetchCollection({collection, query}); 14 | router.push(collection.appUrl(params)); 15 | }; 16 | 17 | render() { 18 | const {collection} = this.props; 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 37 | 38 | 39 | ); 40 | } 41 | } 42 | 43 | GenericPagination.contextTypes = { 44 | router: React.PropTypes.object 45 | }; 46 | 47 | export default GenericPagination; 48 | -------------------------------------------------------------------------------- /frontend/src/app/components/PaginationInfo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class PaginationInfo extends React.Component { 5 | render() { 6 | const {collection} = this.props; 7 | 8 | return ( 9 | 10 | Showing {collection.pagination.start_index} to {collection.models.size} of {collection.pagination.total_entries} entries 11 | 12 | ); 13 | } 14 | } 15 | 16 | export default PaginationInfo; 17 | -------------------------------------------------------------------------------- /frontend/src/app/components/RefreshButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class RefreshButton extends React.Component { 5 | handleClick = () => { 6 | const {actions, collection} = this.props; 7 | const {router} = this.context; 8 | const query = collection.get("query"); 9 | actions.fetchCollection({collection, query}); 10 | router.push(collection.appUrl()); 11 | }; 12 | 13 | render() { 14 | return ( 15 | 19 | Refresh 20 | 21 | ); 22 | } 23 | } 24 | 25 | RefreshButton.contextTypes = { 26 | router: React.PropTypes.object 27 | }; 28 | 29 | export default RefreshButton; 30 | -------------------------------------------------------------------------------- /frontend/src/app/components/RouteNotFound.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import history from "app/history"; 4 | 5 | 6 | class RouteNotFound extends React.Component { 7 | render() { 8 | return( 9 |
    10 |

    404

    11 |
    12 |

    Oops! Page not found.

    13 | 14 |

    15 | We could not find the page you were looking for. 16 |

    17 | 18 | 22 | Go Back 23 | 24 |
    25 |
    26 | ); 27 | } 28 | } 29 | 30 | export default RouteNotFound; 31 | -------------------------------------------------------------------------------- /frontend/src/app/components/SearchBox.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classnames from "classnames"; 3 | 4 | 5 | class SearchBox extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | const {collection} = props; 9 | this.state = {query: collection.get("query")}; 10 | } 11 | 12 | componentWillReceiveProps(nextProps) { 13 | const {props} = this; 14 | const query = nextProps.collection.get("query"); 15 | 16 | if(props.collection.get("query") !== query) { 17 | this.setState({query}); 18 | } 19 | } 20 | 21 | handleClear = () => { 22 | const {actions, collection, params} = this.props; 23 | const {router} = this.context; 24 | let query = collection.get("query"); 25 | 26 | query = query.withMutations((map) => { 27 | map.set("page", 1); 28 | map.set("search", ""); 29 | }); 30 | 31 | this.setState({query}); 32 | actions.fetchCollection({collection, query}); 33 | router.push(collection.appUrl(params)); 34 | }; 35 | 36 | handleSubmit = (evnt) => { 37 | const {actions, collection, params} = this.props; 38 | const {router} = this.context; 39 | let {query} = this.state; 40 | 41 | query = query.set("page", 1); 42 | evnt.preventDefault(); 43 | actions.fetchCollection({collection, query}); 44 | router.replace(collection.appUrl(params)); 45 | }; 46 | 47 | handleChange = (evnt) => { 48 | let {query} = this.state; 49 | query = query.set("search", evnt.target.value); 50 | this.setState({query}); 51 | }; 52 | 53 | render() { 54 | const {autoFocus = false, width = "150px"} = this.props; 55 | const {query} = this.state; 56 | const search = query.get("search"); 57 | 58 | const submitButton = classnames({ 59 | "btn": true, 60 | "btn-default": true, 61 | "hide": search.length !== 0 62 | }); 63 | 64 | const clearButton = classnames({ 65 | "btn": true, 66 | "btn-default": true, 67 | "hide": search.length === 0 68 | }); 69 | 70 | return ( 71 |
    72 |
    76 | 85 |
    86 | 92 | 93 | 100 |
    101 |
    102 | 103 | ); 104 | } 105 | } 106 | 107 | SearchBox.contextTypes = { 108 | router: React.PropTypes.object 109 | }; 110 | 111 | export default SearchBox; 112 | -------------------------------------------------------------------------------- /frontend/src/app/components/alerts/InternalServerError.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class InternalServerError extends React.Component { 5 | render() { 6 | return ( 7 |
    8 |

    Internal Server Error

    9 |

    Oops! Something went wrong. We've been notified and will work 10 | on fixing this right away.

    11 |
    12 | ); 13 | } 14 | } 15 | 16 | export default InternalServerError; 17 | -------------------------------------------------------------------------------- /frontend/src/app/components/alerts/SessionExpired.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class SessionExpired extends React.Component { 5 | handleClick = () => { 6 | window.location.reload(); 7 | }; 8 | 9 | render() { 10 | return ( 11 |
    12 |

    Session Expired

    13 |

    14 | 18 | Login 19 | to continue. 20 |

    21 |
    22 | ); 23 | } 24 | } 25 | 26 | export default SessionExpired; 27 | -------------------------------------------------------------------------------- /frontend/src/app/components/higherOrder/README.md: -------------------------------------------------------------------------------- 1 | Inspiration for the pattern found in this directories files came from https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775 2 | -------------------------------------------------------------------------------- /frontend/src/app/components/higherOrder/create.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Button, Modal} from "react-bootstrap"; 3 | import {List} from "immutable"; 4 | 5 | 6 | export default (Component) => { 7 | class CreateForm extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | const {collection} = props; 11 | 12 | this.state = { 13 | show: false, 14 | changeSet: new collection.ChangeSet() 15 | }; 16 | } 17 | 18 | showModal = () => { 19 | this.setState({show: true}); 20 | } 21 | 22 | hideModal = () => { 23 | const {changeSet} = this.state; 24 | 25 | this.setState({ 26 | show: false, 27 | changeSet: changeSet.clear() 28 | }); 29 | } 30 | 31 | handleChange = (evnt) => { 32 | const {changeSet} = this.state; 33 | 34 | this.setState({ 35 | changeSet: changeSet.set(evnt.target.name, evnt.target.value) 36 | }); 37 | } 38 | 39 | handleSubmit = (evnt) => { 40 | const {actions, collection} = this.props; 41 | const {changeSet} = this.state; 42 | const {router} = this.context; 43 | 44 | const successCb = List([ 45 | () => this.hideModal(), 46 | (response) => { 47 | const Model = collection.get("Model"); 48 | const model = new Model(response.body); 49 | this.setState({changeSet: changeSet.clear()}); 50 | router.push(model.appUrl()); 51 | } 52 | ]); 53 | 54 | const errorCb = List([ 55 | (response) => this.setState({ 56 | changeSet: changeSet.set("_errors", response.body) 57 | }) 58 | ]); 59 | 60 | evnt.preventDefault(); 61 | actions.createModel({collection, successCb, errorCb, changeSet}); 62 | } 63 | 64 | render() { 65 | const {collection} = this.props; 66 | const {changeSet} = this.state; 67 | 68 | return( 69 | 70 | Create 71 | 75 | 76 | Create {collection.titleSingular} 77 | 78 | 79 | 85 | 86 | 87 | 91 | 92 | 93 | 94 | 95 | ); 96 | } 97 | } 98 | 99 | CreateForm.contextTypes = { 100 | router: React.PropTypes.object 101 | }; 102 | 103 | return CreateForm; 104 | }; 105 | -------------------------------------------------------------------------------- /frontend/src/app/components/higherOrder/edit.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Button, Modal} from "react-bootstrap"; 3 | import {List} from "immutable"; 4 | 5 | 6 | export default (Component) => { 7 | class EditForm extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | const {model} = props; 11 | 12 | this.state = { 13 | show: false, 14 | saveSuccessful: false, 15 | changeSet: new model.ChangeSet(model.toJS()) 16 | }; 17 | } 18 | 19 | componentWillReceiveProps(nextProps) { 20 | if (this.props.model.id !== nextProps.model.id) { 21 | const {model} = nextProps; 22 | 23 | this.setState({ 24 | changeSet: new model.ChangeSet(model.toJS()), 25 | saveSuccessful: false 26 | }); 27 | } 28 | } 29 | 30 | showModal = () => { 31 | this.setState({show: true}); 32 | } 33 | 34 | hideModal = () => { 35 | this.setState({show: false}); 36 | } 37 | 38 | handleChange = (evnt) => { 39 | const {changeSet} = this.state; 40 | 41 | this.setState({ 42 | changeSet: changeSet.set(evnt.target.name, evnt.target.value) 43 | }); 44 | } 45 | 46 | handleSubmit = (evnt) => { 47 | const {actions, model} = this.props; 48 | const {changeSet} = this.state; 49 | 50 | const successCb = List([ 51 | () => this.hideModal(), 52 | () => this.setState({saveSuccessful: true}) 53 | ]); 54 | 55 | const errorCb = List([ 56 | (response) => this.setState({ 57 | changeSet: changeSet.set("_errors", response.body) 58 | }) 59 | ]); 60 | 61 | evnt.preventDefault(); 62 | actions.saveModel({model, successCb, errorCb, changeSet}); 63 | } 64 | 65 | render() { 66 | const {model} = this.props; 67 | const {changeSet, saveSuccessful} = this.state; 68 | 69 | return( 70 | 71 | {saveSuccessful && 72 | 73 | 74 | 75 | } 76 | Edit 77 | 81 | 82 | Edit {model.toString()} 83 | 84 | 85 | 91 | 92 | 93 | 97 | 98 | 99 | 100 | 101 | ); 102 | } 103 | } 104 | 105 | return EditForm; 106 | }; 107 | -------------------------------------------------------------------------------- /frontend/src/app/components/higherOrder/findModel.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default (Component) => class FindModel extends React.Component { 4 | componentWillMount() { 5 | const {collection, params} = this.props; 6 | const id = params[collection.get("routeId")]; 7 | const models = collection.get("models"); 8 | 9 | if ( ! models.has(id)) { 10 | const {actions} = this.props; 11 | const Model = collection.get("Model"); 12 | actions.fetchModel({model: new Model({id})}); 13 | } 14 | } 15 | 16 | render() { 17 | const {collection, params} = this.props; 18 | const id = params[collection.get("routeId")]; 19 | const Model = collection.get("Model"); 20 | const models = collection.get("models"); 21 | const model = models.get(id, new Model()); 22 | 23 | return ( 24 | 28 | ); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/app/components/higherOrder/hasPermission.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import EmptyComponent from "app/components/EmptyComponent"; 4 | 5 | 6 | export default (Component, permission) => { 7 | return window.django.user.permissions.has(permission) ? Component : EmptyComponent; 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/app/components/higherOrder/query.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Button, Modal} from "react-bootstrap"; 3 | import {List} from "immutable"; 4 | 5 | 6 | export default (Component) => { 7 | class QueryForm extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | const {collection} = props; 11 | 12 | this.state = { 13 | show: false, 14 | query: collection.get("query") 15 | }; 16 | } 17 | 18 | componentWillReceiveProps(nextProps) { 19 | const {props} = this; 20 | const query = nextProps.collection.get("query"); 21 | 22 | if(props.collection.get("query") !== query) { 23 | this.setState({query}); 24 | } 25 | } 26 | 27 | showModal = () => { 28 | this.setState({show: true}); 29 | } 30 | 31 | hideModal = () => { 32 | this.setState({show: false}); 33 | } 34 | 35 | handleChange = (evnt) => { 36 | let {query} = this.state; 37 | query = query.set(evnt.target.name, evnt.target.value); 38 | this.setState({query}); 39 | } 40 | 41 | handleClear = () => { 42 | const {actions, collection} = this.props; 43 | const {router} = this.context; 44 | const successCb = List([() => this.hideModal()]); 45 | let query = collection.get("query"); 46 | 47 | query = query.withMutations((map) => { 48 | map.clear(); 49 | map.set("search", query.get("search")); 50 | map.set("page", 1); 51 | }); 52 | 53 | actions.fetchCollection({collection, query, successCb}); 54 | router.push(collection.appUrl()); 55 | }; 56 | 57 | handleSubmit = (evnt) => { 58 | const {actions, collection} = this.props; 59 | const {router} = this.context; 60 | const {query} = this.state; 61 | const successCb = List([() => this.hideModal()]); 62 | 63 | evnt.preventDefault(); 64 | actions.fetchCollection({collection, query, successCb}); 65 | router.push(collection.appUrl()); 66 | } 67 | 68 | render() { 69 | const {collection} = this.props; 70 | const {query} = this.state; 71 | 72 | return( 73 | 74 | {collection.isFilterActive() && 75 | 76 | 77 | 78 | } 79 | Filters 80 | 84 | 85 | {collection.title} Filters 86 | 87 | 88 | 94 | 95 | 96 | 100 | 101 | 102 | 103 | 104 | 105 | ); 106 | } 107 | } 108 | 109 | QueryForm.contextTypes = { 110 | router: React.PropTypes.object 111 | }; 112 | 113 | return QueryForm; 114 | }; 115 | -------------------------------------------------------------------------------- /frontend/src/app/components/list/Box.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Box} from "adminlte"; 3 | 4 | import LinkedListGroup from "app/components/LinkedListGroup"; 5 | import Pagination from "app/components/Pagination"; 6 | import RefreshButton from "app/components/RefreshButton"; 7 | import SearchBox from "app/components/SearchBox"; 8 | 9 | 10 | class MasterBox extends React.Component { 11 | render() { 12 | const {collection, CreateForm, QueryForm} = this.props; 13 | 14 | return ( 15 | 16 | 17 | {collection.title} 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    25 | 26 | 27 | 28 |
    29 | 30 |
    31 |
    32 | ); 33 | } 34 | } 35 | 36 | export default MasterBox; 37 | -------------------------------------------------------------------------------- /frontend/src/app/components/list/Container.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Col, Row} from "react-bootstrap"; 3 | 4 | import Box from "./Box"; 5 | 6 | 7 | class Container extends React.Component { 8 | componentWillMount() { 9 | const {actions, collection} = this.props; 10 | const query = collection.get("query"); 11 | actions.fetchCollectionIfEmpty({collection, query}); 12 | } 13 | 14 | render() { 15 | const {children, rowOneWidth = 4, rowTwoWidth = 8} = this.props; 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | } 29 | 30 | export default Container; 31 | -------------------------------------------------------------------------------- /frontend/src/app/configureStore.js: -------------------------------------------------------------------------------- 1 | import createLogger from "redux-logger"; 2 | import thunkMiddleware from "redux-thunk"; 3 | import {createStore, applyMiddleware} from "redux"; 4 | 5 | import rootReducer from "./rootReducer"; 6 | 7 | const loggerMiddleware = createLogger({ 8 | collapsed: true 9 | }); 10 | 11 | 12 | export default (initialState) => { 13 | return createStore( 14 | rootReducer, 15 | initialState, 16 | applyMiddleware( 17 | thunkMiddleware, 18 | loggerMiddleware 19 | ) 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/app/constants/alerts.js: -------------------------------------------------------------------------------- 1 | export const ADD_ALERT = "ADD_ALERT"; 2 | 3 | export default { 4 | ADD_ALERT 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/app/constants/http.js: -------------------------------------------------------------------------------- 1 | export function isInformational(code) { 2 | return code >= 100 && code <= 199; 3 | } 4 | 5 | export function isSuccess(code) { 6 | return code >= 200 && code <= 299; 7 | } 8 | 9 | 10 | export function isRedirect(code) { 11 | return code >= 300 && code <= 399; 12 | } 13 | 14 | export function isClientError(code) { 15 | return code >= 400 && code <= 499; 16 | } 17 | 18 | 19 | export function isServerError(code) { 20 | return code >= 500 && code <= 599; 21 | } 22 | 23 | export const HTTP_100_CONTINUE = 100; 24 | export const HTTP_101_SWITCHING_PROTOCOLS = 101; 25 | export const HTTP_200_OK = 200; 26 | export const HTTP_201_CREATED = 201; 27 | export const HTTP_202_ACCEPTED = 202; 28 | export const HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203; 29 | export const HTTP_204_NO_CONTENT = 204; 30 | export const HTTP_205_RESET_CONTENT = 205; 31 | export const HTTP_206_PARTIAL_CONTENT = 206; 32 | export const HTTP_300_MULTIPLE_CHOICES = 300; 33 | export const HTTP_301_MOVED_PERMANENTLY = 301; 34 | export const HTTP_302_FOUND = 302; 35 | export const HTTP_303_SEE_OTHER = 303; 36 | export const HTTP_304_NOT_MODIFIED = 304; 37 | export const HTTP_305_USE_PROXY = 305; 38 | export const HTTP_306_RESERVED = 306; 39 | export const HTTP_307_TEMPORARY_REDIRECT = 307; 40 | export const HTTP_400_BAD_REQUEST = 400; 41 | export const HTTP_401_UNAUTHORIZED = 401; 42 | export const HTTP_402_PAYMENT_REQUIRED = 402; 43 | export const HTTP_403_FORBIDDEN = 403; 44 | export const HTTP_404_NOT_FOUND = 404; 45 | export const HTTP_405_METHOD_NOT_ALLOWED = 405; 46 | export const HTTP_406_NOT_ACCEPTABLE = 406; 47 | export const HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407; 48 | export const HTTP_408_REQUEST_TIMEOUT = 408; 49 | export const HTTP_409_CONFLICT = 409; 50 | export const HTTP_410_GONE = 410; 51 | export const HTTP_411_LENGTH_REQUIRED = 411; 52 | export const HTTP_412_PRECONDITION_FAILED = 412; 53 | export const HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413; 54 | export const HTTP_414_REQUEST_URI_TOO_LONG = 414; 55 | export const HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415; 56 | export const HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416; 57 | export const HTTP_417_EXPECTATION_FAILED = 417; 58 | export const HTTP_428_PRECONDITION_REQUIRED = 428; 59 | export const HTTP_429_TOO_MANY_REQUESTS = 429; 60 | export const HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; 61 | export const HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451; 62 | export const HTTP_500_INTERNAL_SERVER_ERROR = 500; 63 | export const HTTP_501_NOT_IMPLEMENTED = 501; 64 | export const HTTP_502_BAD_GATEWAY = 502; 65 | export const HTTP_503_SERVICE_UNAVAILABLE = 503; 66 | export const HTTP_504_GATEWAY_TIMEOUT = 504; 67 | export const HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505; 68 | export const HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511; 69 | -------------------------------------------------------------------------------- /frontend/src/app/history.js: -------------------------------------------------------------------------------- 1 | import {createHistory} from "history"; 2 | import {useRouterHistory} from "react-router"; 3 | 4 | const browserHistory = useRouterHistory(createHistory)({ 5 | basename: "/app" 6 | }); 7 | 8 | export default browserHistory; 9 | -------------------------------------------------------------------------------- /frontend/src/app/layouts/Admin/MainFooter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | 4 | class Footer extends React.Component { 5 | render() { 6 | return ( 7 |
    8 |
    Anything i want
    9 | Copyright © 2015 Company. 10 | All rights reserved. 11 |
    12 | ); 13 | } 14 | } 15 | 16 | export default Footer; 17 | -------------------------------------------------------------------------------- /frontend/src/app/layouts/Admin/MainHeader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import {MainHeader} from "adminlte"; 4 | 5 | const { 6 | ControlSidebarToggle, 7 | Logo, 8 | MainSidebarToggle, 9 | Nav, 10 | Navbar, 11 | Menu, 12 | UserMenu, 13 | Wrapper 14 | } = MainHeader; 15 | 16 | 17 | class TopNavbar extends React.Component { 18 | render() { 19 | const {actions, adminlte} = this.props; 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | ); 35 | } 36 | } 37 | 38 | export default TopNavbar; 39 | -------------------------------------------------------------------------------- /frontend/src/app/layouts/Admin/MainSidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MainSidebar} from "adminlte"; 3 | 4 | const {Menu, Wrapper} = MainSidebar; 5 | 6 | 7 | class LeftSidebar extends React.Component { 8 | render() { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | } 16 | 17 | export default LeftSidebar; 18 | -------------------------------------------------------------------------------- /frontend/src/app/layouts/Admin/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import actions from "adminlte/actions"; 3 | import {connect} from "react-redux"; 4 | import {createSelector} from "reselect"; 5 | import {bindActionCreators} from "redux"; 6 | import {Content, ContentWrapper, ControlSidebar, Wrapper} from "adminlte"; 7 | 8 | import MainFooter from "./MainFooter"; 9 | import MainSidebar from "./MainSidebar"; 10 | import MainHeader from "./MainHeader"; 11 | 12 | 13 | class Admin extends React.Component { 14 | componentWillMount() { 15 | const {adminlte} = this.props; 16 | const classNames = adminlte.body.get("classNames"); 17 | document.body.className = classNames.join(" "); 18 | } 19 | 20 | render() { 21 | const {adminlte, actions, alerts, children} = this.props; 22 | 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | {alerts.toList().map((Alert, key) => )} 30 | {children} 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | } 39 | 40 | const selector = createSelector( 41 | (state) => state.adminlte, 42 | (state) => state.alerts, 43 | (adminlte, alerts) => { 44 | return { 45 | adminlte, 46 | alerts 47 | }; 48 | } 49 | ); 50 | 51 | const bindActions = (dispatch) => { 52 | return {actions: bindActionCreators(actions, dispatch)}; 53 | }; 54 | 55 | export default connect(selector, bindActions)(Admin); 56 | -------------------------------------------------------------------------------- /frontend/src/app/reducers/alerts.js: -------------------------------------------------------------------------------- 1 | import {OrderedSet} from "immutable"; 2 | 3 | import * as alerts from "app/constants/alerts"; 4 | 5 | const initialState = OrderedSet(); 6 | 7 | 8 | function reducer(state = initialState, action) { 9 | switch (action.type) { 10 | case alerts.ADD_ALERT: 11 | return state.add(action.component); 12 | 13 | default: 14 | return state; 15 | } 16 | } 17 | 18 | export default reducer; 19 | -------------------------------------------------------------------------------- /frontend/src/app/reducers/collection.js: -------------------------------------------------------------------------------- 1 | import {Map} from "immutable"; 2 | import _ from "lodash"; 3 | 4 | 5 | function reducer(state, action, constants) { 6 | switch (action.type) { 7 | case constants.COLLECTION_IS_LOADING: 8 | return state.set("isLoading", true); 9 | 10 | case constants.RECEIVED_COLLECTION: 11 | return state.withMutations((collection) => { 12 | const Model = collection.get("Model"); 13 | const models = _.map(action.models, (data) => new Model(data)); 14 | 15 | collection.set("models", Map(_.indexBy(models, "id"))); 16 | collection.set("isLoading", false); 17 | collection.set("pagination", action.pagination); 18 | }); 19 | 20 | case constants.RECEIVED_MODEL: 21 | const Model = state.get("Model"); 22 | const model = new Model(action.model); 23 | const models = state.get("models"); 24 | // The above call to _.indexBy creates keys that are strings. If we don't 25 | // call .toString() here our key is an integer and a duplicate model 26 | // gets added to the Map. 27 | return state.set("models", models.set(model.id.toString(), model)); 28 | 29 | case constants.REMOVED_MODEL: 30 | return state.withMutations((collection) => { 31 | const models = collection.get("models"); 32 | const pagination = collection.get("pagination"); 33 | const key = action.model.id.toString(); 34 | 35 | pagination.total_entries--; 36 | collection.set("models", models.delete(key)); 37 | }); 38 | 39 | case constants.UPDATE_COLLECTION_QUERY: 40 | return state.set("query", action.query); 41 | 42 | default: 43 | return state; 44 | } 45 | } 46 | 47 | export default reducer; 48 | -------------------------------------------------------------------------------- /frontend/src/app/rootReducer.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from "redux"; 2 | 3 | import adminlte from "adminlte/reducers"; 4 | import alerts from "app/reducers/alerts"; 5 | import users from "app/users/reducers"; 6 | 7 | 8 | export default combineReducers({ 9 | adminlte, 10 | alerts, 11 | users 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/src/app/stylesheets/app.css: -------------------------------------------------------------------------------- 1 | .cursor-pointer { 2 | cursor: pointer; 3 | } 4 | 5 | .inset { 6 | -moz-border-bottom-colors: none; 7 | -moz-border-left-colors: none; 8 | -moz-border-right-colors: none; 9 | -moz-border-top-colors: none; 10 | background: none repeat scroll 0 0 #ffffff; 11 | border-color: #bbbbbb #dadada #dadada; 12 | border-image: none; 13 | border-radius: 6px 6px 6px 6px; 14 | border-right: 1px solid #dadada; 15 | border-style: solid; 16 | border-width: 1px; 17 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.07) inset, 0 1px 0 #dadada; 18 | overflow: auto; 19 | padding: 10px; 20 | position: relative; 21 | z-index: 2; 22 | margin-bottom: 20px; 23 | } 24 | 25 | .nav-tabs-custom >.nav-tabs > li.header > a { 26 | line-height: 35px; 27 | padding: 0 10px; 28 | font-size: 20px; 29 | color: #444; 30 | } 31 | 32 | .btn-box-tool-active { 33 | border-color: #bbbbbb #dadada #dadada; 34 | border-radius: 6px 6px 6px 6px; 35 | border-style: solid; 36 | border-width: 1px; 37 | } 38 | 39 | .btn-app { 40 | min-width: 70px; 41 | padding: 10px 10px; 42 | height: 55px; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/app/stylesheets/index.less: -------------------------------------------------------------------------------- 1 | @import (css) "~bootstrap/dist/css/bootstrap.css"; 2 | @import (css) "~font-awesome/css/font-awesome.css"; 3 | @import (css) "~admin-lte/dist/css/AdminLTE.css"; 4 | @import (css) "~admin-lte/dist/css/skins/skin-purple.css"; 5 | @import (css) "./app.css"; 6 | @import "./nprogress.less"; 7 | -------------------------------------------------------------------------------- /frontend/src/app/stylesheets/nprogress.less: -------------------------------------------------------------------------------- 1 | @base-color: white; 2 | 3 | /* Make clicks pass-through */ 4 | #nprogress { 5 | pointer-events: none; 6 | } 7 | 8 | #nprogress .bar { 9 | background: @base-color; 10 | 11 | position: fixed; 12 | z-index: 1031; 13 | top: 0; 14 | left: 0; 15 | 16 | width: 100%; 17 | height: 2px; 18 | } 19 | 20 | /* Fancy blur effect */ 21 | #nprogress .peg { 22 | display: block; 23 | position: absolute; 24 | right: 0px; 25 | width: 100px; 26 | height: 100%; 27 | box-shadow: 0 0 10px @base-color, 0 0 5px @base-color; 28 | opacity: 1.0; 29 | 30 | -webkit-transform: rotate(3deg) translate(0px, -4px); 31 | -ms-transform: rotate(3deg) translate(0px, -4px); 32 | transform: rotate(3deg) translate(0px, -4px); 33 | } 34 | 35 | /* Remove these to get rid of the spinner */ 36 | #nprogress .spinner { 37 | display: block; 38 | position: fixed; 39 | z-index: 1031; 40 | top: 15px; 41 | right: 15px; 42 | } 43 | 44 | #nprogress .spinner-icon { 45 | width: 18px; 46 | height: 18px; 47 | box-sizing: border-box; 48 | 49 | border: solid 2px transparent; 50 | border-top-color: @base-color; 51 | border-left-color: @base-color; 52 | border-radius: 50%; 53 | 54 | -webkit-animation: nprogress-spinner 400ms linear infinite; 55 | animation: nprogress-spinner 400ms linear infinite; 56 | } 57 | 58 | .nprogress-custom-parent { 59 | overflow: hidden; 60 | position: relative; 61 | } 62 | 63 | .nprogress-custom-parent #nprogress .spinner, 64 | .nprogress-custom-parent #nprogress .bar { 65 | position: absolute; 66 | } 67 | 68 | @-webkit-keyframes nprogress-spinner { 69 | 0% { -webkit-transform: rotate(0deg); } 70 | 100% { -webkit-transform: rotate(360deg); } 71 | } 72 | @keyframes nprogress-spinner { 73 | 0% { transform: rotate(0deg); } 74 | 100% { transform: rotate(360deg); } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /frontend/src/app/urls.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {IndexRedirect, Route} from "react-router"; 3 | 4 | import Admin from "app/layouts/Admin"; 5 | import RouteNotFound from "app/components/RouteNotFound"; 6 | import users from "app/users/urls"; 7 | 8 | 9 | const urls = ( 10 | 11 | 12 | 13 | 14 | {users} 15 | 16 | 17 | 18 | ); 19 | 20 | export default urls; 21 | -------------------------------------------------------------------------------- /frontend/src/app/users/components/CreateForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Input} from "react-bootstrap"; 3 | 4 | import create from "app/components/higherOrder/create"; 5 | import hasPermission from "app/components/higherOrder/hasPermission"; 6 | 7 | 8 | class Form extends React.Component { 9 | render() { 10 | const {changeSet, handleChange, handleSubmit} = this.props; 11 | 12 | return ( 13 |
    14 | 25 | 35 | 46 |