├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
25 | - MENU
26 | {_.map(userLinks, (link, key) =>
27 | -
28 |
29 |
30 | {link.text}
31 |
32 |
33 | )}
34 |
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 |

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 |
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 |
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 |
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 |
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 |
48 | );
49 | }
50 | }
51 |
52 | export default hasPermission(create(Form), "users.add_emailuser");
53 |
--------------------------------------------------------------------------------
/frontend/src/app/users/components/EditForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Input} from "react-bootstrap";
3 |
4 | import edit from "app/components/higherOrder/edit";
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 |
48 | );
49 | }
50 | }
51 |
52 | export default hasPermission(edit(Form), "users.change_emailuser");
53 |
--------------------------------------------------------------------------------
/frontend/src/app/users/components/Model.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 |
4 |
5 | class Model extends React.Component {
6 | render() {
7 | const {model} = this.props;
8 |
9 | return (
10 |
11 | - Id
12 | - {model.id}
13 |
14 | - First Name
15 | - {model.first_name}
16 |
17 | - Last Name
18 | - {model.last_name}
19 |
20 | - Email
21 | - {model.email}
22 |
23 | - Date Joined
24 | - {moment(model.date_joined).format("ddd. MMM. Do YYYY, h:mm A")}
25 |
26 | - Last Login
27 | - {moment(model.last_login).format("ddd. MMM. Do YYYY, h:mm A")}
28 |
29 | - Last Updated
30 | - {moment(model.last_updated).format("ddd. MMM. Do YYYY, h:mm:s A")}
31 |
32 | );
33 | }
34 | }
35 |
36 | export default Model;
37 |
--------------------------------------------------------------------------------
/frontend/src/app/users/components/QueryForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Input} from "react-bootstrap";
3 |
4 | import query from "app/components/higherOrder/query";
5 |
6 |
7 | class Form extends React.Component {
8 | render() {
9 | const {handleChange, handleSubmit, query} = this.props;
10 |
11 | return (
12 |
32 | );
33 | }
34 | }
35 |
36 | export default query(Form);
37 |
--------------------------------------------------------------------------------
/frontend/src/app/users/constants.js:
--------------------------------------------------------------------------------
1 | export const COLLECTION_IS_LOADING = "USERS_IS_LOADING";
2 | export const RECEIVED_COLLECTION = "RECEIVED_USERS";
3 | export const RECEIVED_MODEL = "RECEIVED_USER";
4 | export const REMOVED_MODEL = "REMOVED_MODEL";
5 | export const UPDATE_COLLECTION_QUERY = "UPDATE_USERS_QUERY";
6 |
7 | export default {
8 | COLLECTION_IS_LOADING,
9 | RECEIVED_COLLECTION,
10 | RECEIVED_MODEL,
11 | REMOVED_MODEL,
12 | UPDATE_COLLECTION_QUERY
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/app/users/models.js:
--------------------------------------------------------------------------------
1 | import {Map, Record} from "immutable";
2 |
3 | import isFilterActive from "app/utils/isFilterActive";
4 | import constants from "./constants";
5 |
6 |
7 | class ChangeSet extends Record({
8 | first_name: "",
9 | last_name: "",
10 | email: "",
11 | _errors: Map()
12 | }){}
13 |
14 |
15 | class User extends Record({
16 | id: "0",
17 | constants,
18 | ChangeSet,
19 | date_joined: "",
20 | email: "",
21 | first_name: "",
22 | last_login: "",
23 | last_name: "",
24 | last_updated: ""
25 | }) {
26 | appUrl() {
27 | return `/admin/users/${this.id}`;
28 | }
29 |
30 | tabUrl(tab = "details") {
31 | return `${this.appUrl()}/${tab}`;
32 | }
33 |
34 | apiUrl() {
35 | return `${window.django.urls.users}${this.id}/`;
36 | }
37 |
38 | toString() {
39 | return `${this.first_name} ${this.last_name}`;
40 | }
41 | }
42 |
43 | class Collection extends Record({
44 | apiUrl: window.django.urls.users,
45 | constants,
46 | ChangeSet,
47 | isLoading: false,
48 | Model: User,
49 | models: Map(),
50 | pagination: Map({
51 | end_index: 0,
52 | page: 0,
53 | start_index: 0,
54 | total_pages: 0
55 | }),
56 | query: Map({
57 | page: 1,
58 | search: ""
59 | }),
60 | routeId: "user",
61 | title: "Users",
62 | titleSingular: "User"
63 | }){
64 | appUrl() {
65 | return "/admin/users";
66 | }
67 |
68 | isFilterActive() {
69 | return isFilterActive(this.query);
70 | }
71 | }
72 |
73 | export {
74 | Collection,
75 | User
76 | };
77 |
--------------------------------------------------------------------------------
/frontend/src/app/users/reducers.js:
--------------------------------------------------------------------------------
1 | import collectionReducer from "app/reducers/collection";
2 |
3 | import {Collection} from "./models";
4 |
5 | const initialState = new Collection();
6 |
7 |
8 | function reducer(state = initialState, action) {
9 | return collectionReducer(state, action, state.constants);
10 | }
11 |
12 | export default reducer;
13 |
--------------------------------------------------------------------------------
/frontend/src/app/users/urls.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {IndexRedirect, Route} from "react-router";
3 |
4 | import Detail from "./views/Detail";
5 | import List from "./views/List";
6 | import Tabs from "./views/Tabs";
7 |
8 |
9 | const routes = (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 |
18 | export default routes;
19 |
--------------------------------------------------------------------------------
/frontend/src/app/users/views/Detail.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {bindActionCreators} from "redux";
3 | import {connect} from "react-redux";
4 | import {createSelector} from "reselect";
5 |
6 | import actions from "app/actions/collection";
7 | import DeleteButton from "app/components/DeleteButton";
8 | import EditForm from "app/users/components/EditForm";
9 | import findModel from "app/components/higherOrder/findModel";
10 | import Model from "app/users/components/Model";
11 |
12 |
13 | class Container extends React.Component {
14 | render() {
15 | return (
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | const selector = createSelector(
31 | (state) => state.users,
32 | (collection) => {
33 | return {
34 | collection
35 | };
36 | }
37 | );
38 |
39 | const bindActions = (dispatch) => {
40 | return {actions: bindActionCreators(actions, dispatch)};
41 | };
42 |
43 | export default connect(selector, bindActions)(findModel(Container));
44 |
--------------------------------------------------------------------------------
/frontend/src/app/users/views/List.js:
--------------------------------------------------------------------------------
1 | import {bindActionCreators} from "redux";
2 | import {connect} from "react-redux";
3 | import {createSelector} from "reselect";
4 |
5 | import actions from "app/actions/collection";
6 | import Container from "app/components/list/Container";
7 | import CreateForm from "app/users/components/CreateForm";
8 | import QueryForm from "app/users/components/QueryForm";
9 |
10 |
11 | const selector = createSelector(
12 | (state) => state.users,
13 | (collection) => {
14 | return {
15 | collection,
16 | CreateForm,
17 | QueryForm
18 | };
19 | }
20 | );
21 |
22 | const bindActions = (dispatch) => {
23 | return {actions: bindActionCreators(actions, dispatch)};
24 | };
25 |
26 | export default connect(selector, bindActions)(Container);
27 |
--------------------------------------------------------------------------------
/frontend/src/app/users/views/Tabs.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {bindActionCreators} from "redux";
3 | import {connect} from "react-redux";
4 | import {createSelector} from "reselect";
5 | import {Tab, Tabs} from "react-bootstrap";
6 |
7 | import actions from "app/actions/collection";
8 | import findModel from "app/components/higherOrder/findModel";
9 |
10 |
11 | class Container extends React.Component {
12 | componentWillMount() {
13 | const {props} = this;
14 | this.setState({activeKey: props.children.props.route.path});
15 | }
16 |
17 | componentWillReceiveProps(nextProps) {
18 | const {props} = this;
19 |
20 | if (
21 | (props.params.user !== nextProps.params.user) &&
22 | (props.children.props.route.path !== nextProps.children.props.route.path)
23 | ) {
24 | const {model} = nextProps;
25 | const {router} = this.context;
26 | const activeKey = nextProps.children.props.route.path;
27 | router.push(model.tabUrl(activeKey));
28 | this.setState({activeKey});
29 | }
30 | }
31 |
32 | handleSelect = (activeKey) => {
33 | const {model} = this.props;
34 | const {router} = this.context;
35 | router.push(model.tabUrl(activeKey));
36 | this.setState({activeKey});
37 | };
38 |
39 | render() {
40 | const {children, model} = this.props;
41 | const {activeKey} = this.state;
42 |
43 | return (
44 |
50 |
55 |
60 | {activeKey === "details" && children}
61 |
62 |
63 | );
64 | }
65 | }
66 |
67 | Container.contextTypes = {
68 | router: React.PropTypes.object
69 | };
70 |
71 | const selector = createSelector(
72 | (state) => state.users,
73 | (collection) => {
74 | return {
75 | collection
76 | };
77 | }
78 | );
79 |
80 | const bindActions = (dispatch) => {
81 | return {actions: bindActionCreators(actions, dispatch)};
82 | };
83 |
84 | export default connect(selector, bindActions)(findModel(Container));
85 |
--------------------------------------------------------------------------------
/frontend/src/app/utils/http.js:
--------------------------------------------------------------------------------
1 | import nprogress from "nprogress";
2 | import superagent from "superagent";
3 |
4 |
5 | function del(url, successCb, errorCb) {
6 | let request = superagent.del(url);
7 | request.set("Accept", "application/json");
8 | request.set("X-CSRFToken", window.django.csrf);
9 | nprogress.start();
10 |
11 | request.end((error, response) => {
12 | nprogress.done();
13 |
14 | if (error) {
15 | errorCb.forEach((func) => func(response));
16 | } else {
17 | successCb.forEach((func) => func(response));
18 | }
19 | });
20 | }
21 |
22 | function get(url, query = {}, successCb, errorCb) {
23 | let request = superagent.get(url);
24 | request.set("Accept", "application/json");
25 | request.query(query);
26 | nprogress.start();
27 |
28 | request.end((error, response) => {
29 | nprogress.done();
30 |
31 | if (error) {
32 | errorCb.forEach((func) => func(response));
33 | } else {
34 | successCb.forEach((func) => func(response));
35 | }
36 | });
37 | }
38 |
39 | function put(url, data = {}, successCb, errorCb) {
40 | const request = superagent.put(url);
41 | request.set("Accept", "application/json");
42 | request.set("X-CSRFToken", window.django.csrf);
43 | request.send(data);
44 | nprogress.start();
45 |
46 | request.end((error, response) => {
47 | nprogress.done();
48 |
49 | if (error) {
50 | errorCb.forEach((func) => func(response));
51 | } else {
52 | successCb.forEach((func) => func(response));
53 | }
54 | });
55 | }
56 |
57 | function post(url, data = {}, successCb, errorCb) {
58 | const request = superagent.post(url);
59 | request.set("Accept", "application/json");
60 | request.set("X-CSRFToken", window.django.csrf);
61 | request.send(data);
62 | nprogress.start();
63 |
64 | request.end((error, response) => {
65 | nprogress.done();
66 |
67 | if (error) {
68 | errorCb.forEach((func) => func(response));
69 | } else {
70 | successCb.forEach((func) => func(response));
71 | }
72 | });
73 | }
74 |
75 |
76 | export default {
77 | del,
78 | get,
79 | post,
80 | put
81 | };
82 |
--------------------------------------------------------------------------------
/frontend/src/app/utils/isFilterActive.js:
--------------------------------------------------------------------------------
1 | import {Set} from "immutable";
2 | import _ from "lodash";
3 |
4 |
5 | const isFilterActive = (query, excluded = Set(["search"])) => {
6 | const strings = query.filter((value) => _.isString(value));
7 | const filterInputs = strings.filterNot((value, key) => excluded.has(key));
8 |
9 | const filtersLengthSum = filterInputs.reduce((length, value) => {
10 | return length += value.length;
11 | }, 0);
12 |
13 | return filtersLengthSum > 0;
14 | };
15 |
16 | export default isFilterActive;
17 |
--------------------------------------------------------------------------------
/frontend/src/app/utils/viewportDimensions.js:
--------------------------------------------------------------------------------
1 | const element = document.documentElement;
2 | const $body = document.getElementsByTagName("body")[0];
3 |
4 | // http://tinyurl.com/phansux
5 | function viewportDimensions() {
6 | return {
7 | width: window.innerWidth || element.clientWidth || $body.clientWidth,
8 | height: window.innerHeight || element.clientHeight || $body.clientHeight
9 | };
10 | }
11 |
12 | export default viewportDimensions;
13 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import Root from "app/Root";
5 |
6 | const $root = document.getElementById("root");
7 |
8 | ReactDOM.render(, $root);
9 |
--------------------------------------------------------------------------------
/frontend/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var path = require("path");
2 | var webpack = require("webpack");
3 | var ExtractTextPlugin = require("extract-text-webpack-plugin");
4 |
5 |
6 | module.exports = {
7 | devtool: "cheap-module-eval-source-map",
8 | entry: {
9 | app: "./src/index",
10 | vendor: [
11 | "classnames", "history", "immutable", "lodash", "moment",
12 | "nprogress", "react", "react-bootstrap", "react-dom", "react-redux",
13 | "react-router", "redux", "redux-logger", "redux-thunk",
14 | "reselect", "superagent"
15 | ],
16 | },
17 | resolve: {
18 | modulesDirectories: ["src", "node_modules"]
19 | },
20 | output: {
21 | path: path.join(__dirname, "build"),
22 | filename: "js/bundle.js",
23 | },
24 | plugins: [
25 | new webpack.optimize.CommonsChunkPlugin("vendor", "js/vendor.bundle.js"),
26 | new ExtractTextPlugin("stylesheets.css"),
27 | new webpack.NoErrorsPlugin()
28 | ],
29 | module: {
30 | loaders: [
31 | {
32 | test: /\.js$/,
33 | loaders: ["babel"],
34 | include: path.join(__dirname, "src")
35 | },
36 | {
37 | // expose immutable globally so we can use it in app.html
38 | test: require.resolve("immutable"),
39 | loader: "expose?immutable"
40 | },
41 | {
42 | test: /\.less$/,
43 | loader: ExtractTextPlugin.extract("css?sourceMap!less?sourceMap")
44 | },
45 | {
46 | // move font files found within CSS to the build directory
47 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
48 | loader: "file?name=[path][name].[ext]?[hash]&context=./node_modules"
49 | },
50 | {
51 | // move images found within CSS to the build directory
52 | test: /\.(jpg|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
53 | loader: "file?name=[path][name].[ext]?[hash]&context=./node_modules"
54 | }
55 | ]
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/frontend/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | var path = require("path");
2 | var webpack = require("webpack");
3 | var ExtractTextPlugin = require("extract-text-webpack-plugin");
4 |
5 |
6 | module.exports = {
7 | devtool: "source-map",
8 | entry: {
9 | app: "./src/index",
10 | vendor: [
11 | "classnames", "history", "immutable", "lodash", "moment",
12 | "nprogress", "react", "react-bootstrap", "react-dom", "react-redux",
13 | "react-router", "redux", "redux-logger", "redux-thunk",
14 | "reselect", "superagent"
15 | ],
16 | },
17 | resolve: {
18 | modulesDirectories: ["src", "node_modules"]
19 | },
20 | output: {
21 | path: path.join(__dirname, "dist"),
22 | filename: "js/bundle.js",
23 | publicPath: "/static/"
24 | },
25 | plugins: [
26 | new webpack.optimize.CommonsChunkPlugin("vendor", "js/vendor.bundle.js"),
27 | new webpack.optimize.OccurenceOrderPlugin(),
28 | new ExtractTextPlugin("stylesheets.css"),
29 | new webpack.DefinePlugin({
30 | "process.env": {
31 | "NODE_ENV": JSON.stringify("production")
32 | }
33 | }),
34 | new webpack.optimize.UglifyJsPlugin({
35 | compressor: {
36 | warnings: false
37 | }
38 | })
39 | ],
40 | module: {
41 | loaders: [
42 | {
43 | test: /\.js$/,
44 | loaders: ["babel"],
45 | include: path.join(__dirname, "src")
46 | },
47 | {
48 | // expose immutable globally so we can use it in app.html
49 | test: require.resolve("immutable"),
50 | loader: "expose?immutable"
51 | },
52 | {
53 | test: /\.less$/,
54 | loader: ExtractTextPlugin.extract("css?sourceMap!less?sourceMap")
55 | },
56 | {
57 | // move font files found within CSS to the build directory
58 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
59 | loader: "file?name=[path][name].[ext]?[hash]&context=./node_modules"
60 | },
61 | {
62 | // move images found within CSS to the build directory
63 | test: /\.(jpg|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
64 | loader: "file?name=[path][name].[ext]?[hash]&context=./node_modules"
65 | }
66 | ]
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | dj-database-url==0.4.1
2 | Django==1.11.1
3 | django-extensions==1.6.1
4 | django-filter==1.0.4
5 | django-material==1.0.0
6 | djangorestframework==3.6.3
7 | gunicorn==19.6.0
8 | psycopg2==2.6.2
9 | python-env==1.0.0
10 | whitenoise==3.2
11 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.6.1
2 |
--------------------------------------------------------------------------------