├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── core
├── __init__.py
├── admin.py
├── apps.py
├── management
│ └── commands
│ │ └── create_data.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20190404_0806.py
│ └── __init__.py
├── models.py
├── serializers.py
├── tests.py
└── views.py
├── db.sqlite3
├── djfilter
├── __init__.py
├── settings
│ ├── __init__.py
│ ├── base.py
│ ├── dev.py
│ └── prod.py
├── urls.py
└── wsgi
│ ├── dev.py
│ └── prod.py
├── manage.py
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── requirements.txt
├── src
├── App.js
├── App.test.js
├── components
│ └── Result.js
├── containers
│ ├── Form.js
│ ├── InfiniteScrollResults.js
│ ├── Layout.js
│ ├── Login.js
│ ├── Results.js
│ └── Signup.js
├── hoc
│ └── hoc.js
├── index.js
├── registerServiceWorker.js
├── routes.js
├── store
│ ├── actions
│ │ ├── actionTypes.js
│ │ └── auth.js
│ ├── reducers
│ │ └── auth.js
│ └── utility.js
└── style.css
├── static
└── template.css
├── templates
└── bootstrap_form.html
├── thumbnail.png
└── titles.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | env
2 | src/static_root
3 | src/media_root
4 | *.pyc
5 | __pycache__/
6 | node_modules
7 | build
8 | package-lock.json
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "${workspaceFolder}/env/bin/python3",
3 | "editor.formatOnSave": true,
4 | "python.linting.pep8Enabled": true,
5 | "python.linting.pylintPath": "pylint",
6 | "python.linting.pylintArgs": ["--load-plugins", "pylint_django"],
7 | "python.linting.pylintEnabled": true,
8 | "python.venvPath": "${workspaceFolder}/env/bin/python3",
9 | "python.linting.pep8Args": ["--ignore=E501"],
10 | "files.exclude": {
11 | "**/*.pyc": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | The Definitive Django Learning Platform.
9 |
10 |
11 |
12 | ### *** Deprecation Warning ***
13 |
14 | This project is over two years old so you should assume it is outdated. You can still watch the [tutorials](https://youtu.be/-X1KMCM_uts) to learn how to build this project.
15 |
16 | # Django Filtering Form
17 |
18 | This project showcases an advanced filtering form. At first the form is built with Bootstrap and later on with React.
19 |
20 |
21 |
22 |
23 |
24 | ### Tutorial and commit links
25 |
26 | Use `git reset -- hard ` along with the commit hash for each video below to reset back to the starting code for that video
27 |
28 | 1. (Getting started)[https://youtu.be/-X1KMCM_uts]
29 | 2. (bf7155dc357d259c38a1f06cb96bdfe80869b89e)[https://youtu.be/V-xXdsi91wc]
30 | 3. (75792ade18df186aa1f530de09348349f70b0fdd)[https://youtu.be/zl_jsCLo4eo]
31 | 4. (486710c9f149469bedfe7c01a4a65b96cd849fad)[https://youtu.be/1jU8kttXYc4]
32 | 5. (9513363ae33c80d8181c08ad11cb1056cac1bd55)[https://youtu.be/vU0VeFN-abU]
33 | 6. (c53238bb6f71f33ee7f40fb4049d902377d0e170)[https://youtu.be/n1_MQiSXyxw]
34 | 7. (2960eb9a05c8bbc66c829e1cd751d5f9be6d70f9)[https://youtu.be/GtwK0Hj6jU8]
35 | 8. (e7e37bbf269562900d2e47323802dc5880afb34c)[https://youtu.be/EMR8jlkAbCA]
36 | 9. (6fdbf72acec763380a368460a9d436f26451356f)[https://youtu.be/bK2gZymltRI]
37 | 10. (01e9e69434a757df68883000232bd39e08240edd)[https://youtu.be/4W8ZJPPNn9k]
38 |
39 | ---
40 |
41 |
42 |
43 |
Other places you can find us:
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/djfilter/4c41cecad978d5a19be0803de7a245622a20e1a7/core/__init__.py
--------------------------------------------------------------------------------
/core/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Author, Category, Journal
4 |
5 | admin.site.register(Author)
6 | admin.site.register(Category)
7 | admin.site.register(Journal)
8 |
--------------------------------------------------------------------------------
/core/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class CoreConfig(AppConfig):
5 | name = 'core'
6 |
--------------------------------------------------------------------------------
/core/management/commands/create_data.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import random
3 | from django.core.management.base import BaseCommand
4 | from core.models import Journal, Category, Author
5 |
6 |
7 | categories = [
8 | 'Sport',
9 | 'Lifestyle',
10 | 'Music',
11 | 'Coding',
12 | 'Travelling'
13 | ]
14 |
15 | authors = [
16 | 'John', 'Michael', 'Luke', 'Sally', 'Joe', 'Dude', 'Guy', 'Barbara'
17 | ]
18 |
19 |
20 | def generate_author_name():
21 | index = random.randint(0, 7)
22 | return authors[index]
23 |
24 |
25 | def generate_category_name():
26 | index = random.randint(0, 4)
27 | return categories[index]
28 |
29 |
30 | def generate_view_count():
31 | return random.randint(0, 100)
32 |
33 |
34 | def generate_is_reviewed():
35 | val = random.randint(0, 1)
36 | if val == 0:
37 | return False
38 | return True
39 |
40 |
41 | def generate_publish_date():
42 | year = random.randint(2000, 2030)
43 | month = random.randint(1, 12)
44 | day = random.randint(1, 28)
45 | return datetime.date(year, month, day)
46 |
47 |
48 | class Command(BaseCommand):
49 |
50 | def add_arguments(self, parser):
51 | parser.add_argument(
52 | 'file_name', type=str, help='The txt file that contains the journal titles.')
53 |
54 | def handle(self, *args, **kwargs):
55 | file_name = kwargs['file_name']
56 | with open(f'{file_name}.txt') as file:
57 | for row in file:
58 | title = row
59 | author_name = generate_author_name()
60 | category_name = generate_category_name()
61 | publish_date = generate_publish_date()
62 | views = generate_view_count()
63 | reviewed = generate_is_reviewed()
64 |
65 | author = Author.objects.get_or_create(
66 | name=author_name
67 | )
68 |
69 | journal = Journal(
70 | title=title,
71 | author=Author.objects.get(name=author_name),
72 | publish_date=publish_date,
73 | views=views,
74 | reviewed=reviewed
75 | )
76 |
77 | journal.save()
78 |
79 | category = Category.objects.get_or_create(name=category_name)
80 |
81 | journal.categories.add(
82 | Category.objects.get(name=category_name))
83 |
84 | self.stdout.write(self.style.SUCCESS('Data imported successfully'))
85 |
--------------------------------------------------------------------------------
/core/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2 on 2019-04-03 15:31
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Author',
17 | fields=[
18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('name', models.CharField(max_length=30)),
20 | ],
21 | ),
22 | migrations.CreateModel(
23 | name='Category',
24 | fields=[
25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
26 | ('name', models.CharField(max_length=20)),
27 | ],
28 | ),
29 | migrations.CreateModel(
30 | name='Journal',
31 | fields=[
32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
33 | ('title', models.CharField(max_length=120)),
34 | ('publish_date', models.DateTimeField(auto_now_add=True)),
35 | ('views', models.IntegerField(default=0)),
36 | ('reviewed', models.BooleanField(default=False)),
37 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Author')),
38 | ('categories', models.ManyToManyField(to='core.Category')),
39 | ],
40 | ),
41 | ]
42 |
--------------------------------------------------------------------------------
/core/migrations/0002_auto_20190404_0806.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2 on 2019-04-04 08:06
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('core', '0001_initial'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='journal',
15 | name='publish_date',
16 | field=models.DateTimeField(),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/core/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/djfilter/4c41cecad978d5a19be0803de7a245622a20e1a7/core/migrations/__init__.py
--------------------------------------------------------------------------------
/core/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Author(models.Model):
5 | name = models.CharField(max_length=30)
6 |
7 | def __str__(self):
8 | return self.name
9 |
10 |
11 | class Category(models.Model):
12 | name = models.CharField(max_length=20)
13 |
14 | def __str__(self):
15 | return self.name
16 |
17 |
18 | class Journal(models.Model):
19 | title = models.CharField(max_length=120)
20 | author = models.ForeignKey(Author, on_delete=models.CASCADE)
21 | categories = models.ManyToManyField(Category)
22 | publish_date = models.DateTimeField()
23 | views = models.IntegerField(default=0)
24 | reviewed = models.BooleanField(default=False)
25 |
26 | def __str__(self):
27 | return self.title
28 |
--------------------------------------------------------------------------------
/core/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Journal, Category, Author
3 |
4 |
5 | class StringSerializer(serializers.StringRelatedField):
6 | def to_internal_value(self, value):
7 | return value
8 |
9 |
10 | class JournalSerializer(serializers.ModelSerializer):
11 | author = StringSerializer(many=False)
12 | categories = StringSerializer(many=True)
13 |
14 | class Meta:
15 | model = Journal
16 | fields = ('__all__')
17 |
--------------------------------------------------------------------------------
/core/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/core/views.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Q, Count
2 | from django.shortcuts import render, get_object_or_404
3 | from rest_framework import generics
4 | from rest_framework.response import Response
5 | from .models import Journal, Category
6 | from .serializers import JournalSerializer
7 |
8 |
9 | def is_valid_queryparam(param):
10 | return param != '' and param is not None
11 |
12 |
13 | def filter(request):
14 | qs = Journal.objects.all()
15 | categories = Category.objects.all()
16 | title_contains_query = request.GET.get('title_contains')
17 | id_exact_query = request.GET.get('id_exact')
18 | title_or_author_query = request.GET.get('title_or_author')
19 | view_count_min = request.GET.get('view_count_min')
20 | view_count_max = request.GET.get('view_count_max')
21 | date_min = request.GET.get('date_min')
22 | date_max = request.GET.get('date_max')
23 | category = request.GET.get('category')
24 | reviewed = request.GET.get('reviewed')
25 | not_reviewed = request.GET.get('notReviewed')
26 |
27 | if is_valid_queryparam(title_contains_query):
28 | qs = qs.filter(title__icontains=title_contains_query)
29 |
30 | elif is_valid_queryparam(id_exact_query):
31 | qs = qs.filter(id=id_exact_query)
32 |
33 | elif is_valid_queryparam(title_or_author_query):
34 | qs = qs.filter(Q(title__icontains=title_or_author_query)
35 | | Q(author__name__icontains=title_or_author_query)
36 | ).distinct()
37 |
38 | if is_valid_queryparam(view_count_min):
39 | qs = qs.filter(views__gte=view_count_min)
40 |
41 | if is_valid_queryparam(view_count_max):
42 | qs = qs.filter(views__lt=view_count_max)
43 |
44 | if is_valid_queryparam(date_min):
45 | qs = qs.filter(publish_date__gte=date_min)
46 |
47 | if is_valid_queryparam(date_max):
48 | qs = qs.filter(publish_date__lt=date_max)
49 |
50 | if is_valid_queryparam(category) and category != 'Choose...':
51 | qs = qs.filter(categories__name=category)
52 |
53 | if reviewed == 'on':
54 | qs = qs.filter(reviewed=True)
55 |
56 | elif not_reviewed == 'on':
57 | qs = qs.filter(reviewed=False)
58 |
59 | return qs
60 |
61 |
62 | def infinite_filter(request):
63 | limit = request.GET.get('limit')
64 | offset = request.GET.get('offset')
65 | return Journal.objects.all()[int(offset): int(offset) + int(limit)]
66 |
67 |
68 | def is_there_more_data(request):
69 | offset = request.GET.get('offset')
70 | if int(offset) > Journal.objects.all().count():
71 | return False
72 | return True
73 |
74 |
75 | def BootstrapFilterView(request):
76 | qs = filter(request)
77 | context = {
78 | 'queryset': qs,
79 | 'categories': Category.objects.all()
80 | }
81 | return render(request, "bootstrap_form.html", context)
82 |
83 |
84 | class ReactFilterView(generics.ListAPIView):
85 | serializer_class = JournalSerializer
86 |
87 | def get_queryset(self):
88 | qs = filter(self.request)
89 | return qs
90 |
91 |
92 | class ReactInfiniteView(generics.ListAPIView):
93 | serializer_class = JournalSerializer
94 |
95 | def get_queryset(self):
96 | qs = infinite_filter(self.request)
97 | return qs
98 |
99 | def list(self, request):
100 | queryset = self.get_queryset()
101 | serializer = self.serializer_class(queryset, many=True)
102 | return Response({
103 | "journals": serializer.data,
104 | "has_more": is_there_more_data(request)
105 | })
106 |
--------------------------------------------------------------------------------
/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/djfilter/4c41cecad978d5a19be0803de7a245622a20e1a7/db.sqlite3
--------------------------------------------------------------------------------
/djfilter/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/djfilter/4c41cecad978d5a19be0803de7a245622a20e1a7/djfilter/__init__.py
--------------------------------------------------------------------------------
/djfilter/settings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/djfilter/4c41cecad978d5a19be0803de7a245622a20e1a7/djfilter/settings/__init__.py
--------------------------------------------------------------------------------
/djfilter/settings/base.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | BASE_DIR = os.path.dirname(os.path.dirname(
4 | os.path.dirname(os.path.abspath(__file__))))
5 | SECRET_KEY = '-05sgp9!deq=q1nltm@^^2cc+v29i(tyybv3v2t77qi66czazj'
6 | DEBUG = True
7 | ALLOWED_HOSTS = []
8 |
9 | INSTALLED_APPS = [
10 | 'django.contrib.admin',
11 | 'django.contrib.auth',
12 | 'django.contrib.contenttypes',
13 | 'django.contrib.sessions',
14 | 'django.contrib.messages',
15 | 'django.contrib.staticfiles',
16 |
17 | 'django.contrib.sites',
18 | 'allauth',
19 | 'allauth.account',
20 | 'allauth.socialaccount',
21 | 'corsheaders',
22 | 'rest_auth',
23 | 'rest_auth.registration',
24 | 'rest_framework',
25 | 'rest_framework.authtoken',
26 |
27 | 'core'
28 | ]
29 |
30 | MIDDLEWARE = [
31 | 'corsheaders.middleware.CorsMiddleware',
32 | 'django.middleware.security.SecurityMiddleware',
33 | 'django.contrib.sessions.middleware.SessionMiddleware',
34 | 'django.middleware.common.CommonMiddleware',
35 | 'django.middleware.csrf.CsrfViewMiddleware',
36 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
37 | 'django.contrib.messages.middleware.MessageMiddleware',
38 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
39 | ]
40 |
41 | ROOT_URLCONF = 'djfilter.urls'
42 |
43 | TEMPLATES = [
44 | {
45 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
46 | 'DIRS': [os.path.join(BASE_DIR, 'build'), os.path.join(BASE_DIR, 'templates')],
47 | 'APP_DIRS': True,
48 | 'OPTIONS': {
49 | 'context_processors': [
50 | 'django.template.context_processors.debug',
51 | 'django.template.context_processors.request',
52 | 'django.contrib.auth.context_processors.auth',
53 | 'django.contrib.messages.context_processors.messages',
54 | ],
55 | },
56 | },
57 | ]
58 |
59 | LANGUAGE_CODE = 'en-us'
60 | TIME_ZONE = 'UTC'
61 | USE_I18N = True
62 | USE_L10N = True
63 | USE_TZ = True
64 |
65 | STATIC_URL = '/static/'
66 | MEDIA_URL = '/media/'
67 | STATICFILES_DIRS = [
68 | os.path.join(BASE_DIR, 'build/static'),
69 | os.path.join(BASE_DIR, 'static')
70 | ]
71 | STATIC_ROOT = os.path.join(BASE_DIR, 'static_root')
72 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media_root')
73 |
74 | SITE_ID = 1
75 |
76 | REST_FRAMEWORK = {
77 | 'DEFAULT_PERMISSION_CLASSES': (
78 | 'rest_framework.permissions.IsAuthenticatedOrReadOnly',
79 | ),
80 | 'DEFAULT_AUTHENTICATION_CLASSES': (
81 | 'rest_framework.authentication.TokenAuthentication',
82 | 'rest_framework.authentication.SessionAuthentication',
83 | 'rest_framework.authentication.BasicAuthentication',
84 | ),
85 | }
86 |
87 | CSRF_COOKIE_NAME = "csrftoken"
88 |
89 | ACCOUNT_EMAIL_REQUIRED = False
90 | ACCOUNT_AUTHENTICATION_METHOD = 'username'
91 | ACCOUNT_EMAIL_VERIFICATION = 'none'
92 |
--------------------------------------------------------------------------------
/djfilter/settings/dev.py:
--------------------------------------------------------------------------------
1 | '''Use this for development'''
2 |
3 | from .base import *
4 |
5 | ALLOWED_HOSTS += ['127.0.0.1']
6 | DEBUG = True
7 |
8 | WSGI_APPLICATION = 'djfilter.wsgi.dev.application'
9 |
10 | DATABASES = {
11 | 'default': {
12 | 'ENGINE': 'django.db.backends.sqlite3',
13 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
14 | }
15 | }
16 |
17 | CORS_ORIGIN_WHITELIST = (
18 | 'localhost:3000',
19 | )
20 |
--------------------------------------------------------------------------------
/djfilter/settings/prod.py:
--------------------------------------------------------------------------------
1 | '''Use this for production'''
2 |
3 | from .base import *
4 |
5 | DEBUG = False
6 | ALLOWED_HOSTS += ['http://domain.com']
7 | WSGI_APPLICATION = 'djfilter.wsgi.prod.application'
8 |
9 | DATABASES = {
10 | 'default': {
11 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
12 | 'NAME': 'db_name',
13 | 'USER': 'db_user',
14 | 'PASSWORD': 'db_password',
15 | 'HOST': 'localhost',
16 | 'PORT': '',
17 | }
18 | }
19 |
20 | AUTH_PASSWORD_VALIDATORS = [
21 | {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
22 | {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
23 | {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
24 | {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
25 | ]
26 |
27 | STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
28 |
--------------------------------------------------------------------------------
/djfilter/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.conf import settings
3 | from django.conf.urls.static import static
4 | from django.urls import path, include, re_path
5 | from django.views.generic import TemplateView
6 | from core.views import BootstrapFilterView, ReactFilterView, ReactInfiniteView
7 |
8 | urlpatterns = [
9 | path('api-auth/', include('rest_framework.urls')),
10 | path('rest-auth/', include('rest_auth.urls')),
11 | path('rest-auth/registration/', include('rest_auth.registration.urls')),
12 | path('admin/', admin.site.urls),
13 | path('', BootstrapFilterView, name='bootstrap'),
14 | path('api/', ReactFilterView.as_view(), name='react'),
15 | path('infinite-api/', ReactInfiniteView.as_view(), name='infinite-react'),
16 | re_path(r'^react/', TemplateView.as_view(template_name='index.html')),
17 | ]
18 |
19 | if settings.DEBUG:
20 | urlpatterns += static(settings.STATIC_URL,
21 | document_root=settings.STATIC_ROOT)
22 | urlpatterns += static(settings.MEDIA_URL,
23 | document_root=settings.MEDIA_ROOT)
24 |
--------------------------------------------------------------------------------
/djfilter/wsgi/dev.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.core.wsgi import get_wsgi_application
4 |
5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djfilter.settings.dev")
6 |
7 | application = get_wsgi_application()
8 |
--------------------------------------------------------------------------------
/djfilter/wsgi/prod.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.core.wsgi import get_wsgi_application
4 | from whitenoise.django import DjangoWhiteNoise
5 |
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djfilter.settings.prod")
7 |
8 | application = get_wsgi_application()
9 | application = DjangoWhiteNoise(application)
10 |
--------------------------------------------------------------------------------
/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', 'djfilter.settings.dev')
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError as exc:
10 | raise ImportError(
11 | "Couldn't import Django. Are you sure it's installed and "
12 | "available on your PYTHONPATH environment variable? Did you "
13 | "forget to activate a virtual environment?"
14 | ) from exc
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "django-react-boilerplate",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^3.7.3",
7 | "axios": "^0.18.0",
8 | "react": "^16.4.1",
9 | "react-dom": "^16.4.1",
10 | "react-redux": "^5.0.7",
11 | "react-router-dom": "^4.3.1",
12 | "react-scripts": "1.1.4",
13 | "redux": "^4.0.0",
14 | "redux-thunk": "^2.3.0"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test --env=jsdom",
20 | "eject": "react-scripts eject",
21 | "postinstall": "npm run build"
22 | },
23 | "engines": {
24 | "node": "9.5.0",
25 | "npm": "6.4.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/djfilter/4c41cecad978d5a19be0803de7a245622a20e1a7/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Django React Boilerplate
10 |
11 |
12 |
13 | You need to enable JavaScript to run this app.
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | certifi==2018.8.24
2 | chardet==3.0.4
3 | defusedxml==0.5.0
4 | Django==2.2
5 | django-allauth==0.37.1
6 | django-cors-headers==2.4.0
7 | django-rest-auth==0.9.3
8 | djangorestframework==3.8.2
9 | idna==2.7
10 | oauthlib==2.1.0
11 | python3-openid==3.1.0
12 | pytz==2018.5
13 | requests==2.20.0
14 | requests-oauthlib==1.0.0
15 | six==1.11.0
16 | sqlparse==0.3.0
17 | urllib3==1.23
18 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { BrowserRouter as Router } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import BaseRouter from "./routes";
5 | import "antd/dist/antd.css";
6 | import "./style.css";
7 | import * as actions from "./store/actions/auth";
8 |
9 | import CustomLayout from "./containers/Layout";
10 |
11 | class App extends Component {
12 | componentDidMount() {
13 | this.props.onTryAutoSignup();
14 | }
15 |
16 | render() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
27 | const mapStateToProps = state => {
28 | return {
29 | isAuthenticated: state.auth.token !== null
30 | };
31 | };
32 |
33 | const mapDispatchToProps = dispatch => {
34 | return {
35 | onTryAutoSignup: () => dispatch(actions.authCheckState())
36 | };
37 | };
38 |
39 | export default connect(
40 | mapStateToProps,
41 | mapDispatchToProps
42 | )(App);
43 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 |
5 | it("renders without crashing", () => {
6 | const div = document.createElement("div");
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/Result.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Result = ({ journal }) => {
4 | return (
5 |
6 |
7 |
8 | Title: {journal.title}{" "}
9 |
10 |
11 | Author: {journal.author.name}{" "}
12 |
13 |
14 | Categories:
15 | {journal.categories.map(c => {
16 | return {c} ;
17 | })}
18 |
19 |
20 | Publish date: {journal.publish_date}{" "}
21 |
22 |
23 | View count: {journal.views}{" "}
24 |
25 |
26 | Reviewed:
27 | {`${journal.reviewed}`}{" "}
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default Result;
36 |
--------------------------------------------------------------------------------
/src/containers/Form.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Form,
4 | Select,
5 | InputNumber,
6 | Input,
7 | Radio,
8 | Button,
9 | DatePicker,
10 | Spin
11 | } from "antd";
12 | import axios from "axios";
13 | import Results from "./Results";
14 |
15 | const Search = Input.Search;
16 | const { Option } = Select;
17 | const { RangePicker } = DatePicker;
18 |
19 | class FilterForm extends React.Component {
20 | state = {
21 | results: [],
22 | loading: false,
23 | error: null
24 | };
25 |
26 | handleSubmit = e => {
27 | e.preventDefault();
28 | this.props.form.validateFields((err, values) => {
29 | const category =
30 | values["category"] === undefined ? null : values["category"];
31 | const view_count_max =
32 | values["maximum-views"] === undefined ? null : values["maximum-views"];
33 | const view_count_min =
34 | values["minimum-views"] === undefined ? null : values["minimum-views"];
35 | let notReviewed = null;
36 | let reviewed =
37 | values["reviewed"] === undefined ? null : values["reviewed"];
38 | if (reviewed === "reviewed") {
39 | reviewed = "on";
40 | notReviewed = null;
41 | } else if (reviewed === "notReviewed") {
42 | reviewed = null;
43 | notReviewed = "on";
44 | }
45 | const title_contains =
46 | values["searchTitle"] === undefined ? null : values["searchTitle"];
47 | const id_exact =
48 | values["searchTitleID"] === undefined ? null : values["searchTitleID"];
49 | const title_or_author =
50 | values["searchTitleOrAuthor"] === undefined
51 | ? null
52 | : values["searchTitleOrAuthor"];
53 | const rangeValue = values["date-range"];
54 | const date_min =
55 | rangeValue === undefined ? null : rangeValue[0].format("YYYY-MM-DD");
56 | const date_max =
57 | rangeValue === undefined ? null : rangeValue[1].format("YYYY-MM-DD");
58 |
59 | this.setState({ loading: true });
60 |
61 | if (!err) {
62 | axios
63 | .get("http://127.0.0.1:8000/api/", {
64 | params: {
65 | title_contains,
66 | id_exact,
67 | title_or_author,
68 | view_count_min,
69 | view_count_max,
70 | date_min,
71 | date_max,
72 | category,
73 | reviewed,
74 | notReviewed
75 | }
76 | })
77 | .then(res => {
78 | this.setState({
79 | loading: false,
80 | results: res.data
81 | });
82 | })
83 | .catch(err => {
84 | this.setState({ error: "There was an error" });
85 | console.log(err);
86 | });
87 | console.log("Received values of form: ", values);
88 | }
89 | });
90 | };
91 |
92 | render() {
93 | const { error, loading, results } = this.state;
94 | const { getFieldDecorator } = this.props.form;
95 | const formItemLayout = {
96 | wrapperCol: { span: 12, offset: 6 }
97 | };
98 | return (
99 |
100 | {error &&
There was an error }
101 |
102 |
104 | Filter Journals
105 |
106 |
107 | {getFieldDecorator("searchTitle")(
108 | console.log(value)}
111 | enterButton
112 | />
113 | )}
114 |
115 |
116 | {getFieldDecorator("searchTitleID")(
117 | console.log(value)}
120 | enterButton
121 | />
122 | )}
123 |
124 |
125 | {getFieldDecorator("searchTitleOrAuthor")(
126 | console.log(value)}
129 | enterButton
130 | />
131 | )}
132 |
133 |
134 | {getFieldDecorator("category")(
135 |
136 | Sport
137 | Lifestyle
138 | Music
139 | Coding
140 | Travelling
141 |
142 | )}
143 |
144 |
145 |
146 | {getFieldDecorator("date-range")( )}
147 |
148 |
149 |
150 | {getFieldDecorator("minimum-views")(
151 |
152 | )}
153 | minimum views
154 |
155 |
156 |
157 | {getFieldDecorator("maximum-views")(
158 |
159 | )}
160 | maximum views
161 |
162 |
163 |
164 | {getFieldDecorator("reviewed")(
165 |
166 | Reviewed
167 | Not Reviewed
168 |
169 | )}
170 |
171 |
172 |
173 |
174 | Submit
175 |
176 |
177 |
178 |
179 | {loading ? (
180 |
181 |
182 |
183 | ) : (
184 |
185 | )}
186 |
187 | );
188 | }
189 | }
190 |
191 | const WrappedFilterForm = Form.create({ name: "validate_other" })(FilterForm);
192 |
193 | export default WrappedFilterForm;
194 |
--------------------------------------------------------------------------------
/src/containers/InfiniteScrollResults.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import axios from "axios";
3 | import Result from "../components/Result";
4 |
5 | class InfiniteResults extends React.PureComponent {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | error: false,
10 | loading: false,
11 | journals: [],
12 | hasMore: true,
13 | offset: 0,
14 | limit: 20
15 | };
16 | window.onscroll = () => {
17 | const {
18 | loadJournals,
19 | state: { error, loading, hasMore }
20 | } = this;
21 | if (error || loading || !hasMore) return;
22 | if (
23 | document.documentElement.scrollHeight -
24 | document.documentElement.scrollTop ===
25 | document.documentElement.clientHeight
26 | ) {
27 | // call some loading method
28 | loadJournals();
29 | }
30 | };
31 | }
32 |
33 | componentWillMount() {
34 | this.loadJournals();
35 | }
36 |
37 | loadJournals = () => {
38 | this.setState({ loading: true }, () => {
39 | const { offset, limit } = this.state;
40 | axios
41 | .get(
42 | `http://127.0.0.1:8000/infinite-api/?limit=${limit}&offset=${offset}`
43 | )
44 | .then(res => {
45 | const newJournals = res.data.journals;
46 | const hasMore = res.data.has_more;
47 | this.setState({
48 | hasMore,
49 | loading: false,
50 | journals: [...this.state.journals, ...newJournals],
51 | offset: offset + limit
52 | });
53 | })
54 | .catch(err => {
55 | this.setState({
56 | error: err.message,
57 | loading: false
58 | });
59 | });
60 | });
61 | };
62 |
63 | render() {
64 | const { error, hasMore, loading, journals } = this.state;
65 | return (
66 |
67 |
Infinite journals
68 |
Scroll down to load more
69 |
70 | {journals.map(j => {
71 | return
;
72 | })}
73 | {error &&
{error}
}
74 | {loading &&
Loading...
}
75 | {!hasMore &&
No more results
}
76 |
77 | );
78 | }
79 | }
80 |
81 | export default InfiniteResults;
82 |
--------------------------------------------------------------------------------
/src/containers/Layout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Layout, Menu, Breadcrumb } from "antd";
3 | import { Link, withRouter } from "react-router-dom";
4 | import { connect } from "react-redux";
5 | import * as actions from "../store/actions/auth";
6 |
7 | const { Header, Content, Footer } = Layout;
8 |
9 | class CustomLayout extends React.Component {
10 | render() {
11 | return (
12 |
13 |
14 |
15 |
21 | {this.props.isAuthenticated ? (
22 |
23 | Logout
24 |
25 | ) : (
26 |
27 | Login
28 |
29 | )}
30 |
31 |
32 |
33 |
34 |
35 | Home
36 |
37 |
38 | List
39 |
40 |
41 |
42 | {this.props.children}
43 |
44 |
45 |
46 | Ant Design ©2016 Created by Ant UED
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | const mapDispatchToProps = dispatch => {
54 | return {
55 | logout: () => dispatch(actions.logout())
56 | };
57 | };
58 |
59 | export default withRouter(
60 | connect(
61 | null,
62 | mapDispatchToProps
63 | )(CustomLayout)
64 | );
65 |
--------------------------------------------------------------------------------
/src/containers/Login.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Icon, Input, Button, Spin } from "antd";
3 | import { connect } from "react-redux";
4 | import { NavLink } from "react-router-dom";
5 | import * as actions from "../store/actions/auth";
6 |
7 | const FormItem = Form.Item;
8 | const antIcon = ;
9 |
10 | class NormalLoginForm extends React.Component {
11 | handleSubmit = e => {
12 | e.preventDefault();
13 | this.props.form.validateFields((err, values) => {
14 | if (!err) {
15 | this.props.onAuth(values.userName, values.password);
16 | this.props.history.push("/");
17 | }
18 | });
19 | };
20 |
21 | render() {
22 | let errorMessage = null;
23 | if (this.props.error) {
24 | errorMessage = {this.props.error.message}
;
25 | }
26 |
27 | const { getFieldDecorator } = this.props.form;
28 | return (
29 |
30 | {errorMessage}
31 | {this.props.loading ? (
32 |
33 | ) : (
34 |
81 | )}
82 |
83 | );
84 | }
85 | }
86 |
87 | const WrappedNormalLoginForm = Form.create()(NormalLoginForm);
88 |
89 | const mapStateToProps = state => {
90 | return {
91 | loading: state.auth.loading,
92 | error: state.auth.error
93 | };
94 | };
95 |
96 | const mapDispatchToProps = dispatch => {
97 | return {
98 | onAuth: (username, password) =>
99 | dispatch(actions.authLogin(username, password))
100 | };
101 | };
102 |
103 | export default connect(
104 | mapStateToProps,
105 | mapDispatchToProps
106 | )(WrappedNormalLoginForm);
107 |
--------------------------------------------------------------------------------
/src/containers/Results.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Result from "../components/Result";
3 |
4 | const Results = ({ journals }) => (
5 |
6 | {journals.map(j => {
7 | return ;
8 | })}
9 |
10 | );
11 |
12 | export default Results;
13 |
--------------------------------------------------------------------------------
/src/containers/Signup.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Input, Icon, Button } from "antd";
3 | import { connect } from "react-redux";
4 | import { NavLink } from "react-router-dom";
5 | import * as actions from "../store/actions/auth";
6 |
7 | const FormItem = Form.Item;
8 |
9 | class RegistrationForm extends React.Component {
10 | state = {
11 | confirmDirty: false
12 | };
13 |
14 | handleSubmit = e => {
15 | e.preventDefault();
16 | this.props.form.validateFieldsAndScroll((err, values) => {
17 | if (!err) {
18 | this.props.onAuth(
19 | values.userName,
20 | values.email,
21 | values.password,
22 | values.confirm
23 | );
24 | this.props.history.push("/");
25 | }
26 | });
27 | };
28 |
29 | handleConfirmBlur = e => {
30 | const value = e.target.value;
31 | this.setState({ confirmDirty: this.state.confirmDirty || !!value });
32 | };
33 |
34 | compareToFirstPassword = (rule, value, callback) => {
35 | const form = this.props.form;
36 | if (value && value !== form.getFieldValue("password")) {
37 | callback("Two passwords that you enter is inconsistent!");
38 | } else {
39 | callback();
40 | }
41 | };
42 |
43 | validateToNextPassword = (rule, value, callback) => {
44 | const form = this.props.form;
45 | if (value && this.state.confirmDirty) {
46 | form.validateFields(["confirm"], { force: true });
47 | }
48 | callback();
49 | };
50 |
51 | render() {
52 | const { getFieldDecorator } = this.props.form;
53 |
54 | return (
55 |
143 | );
144 | }
145 | }
146 |
147 | const WrappedRegistrationForm = Form.create()(RegistrationForm);
148 |
149 | const mapStateToProps = state => {
150 | return {
151 | loading: state.auth.loading,
152 | error: state.auth.error
153 | };
154 | };
155 |
156 | const mapDispatchToProps = dispatch => {
157 | return {
158 | onAuth: (username, email, password1, password2) =>
159 | dispatch(actions.authSignup(username, email, password1, password2))
160 | };
161 | };
162 |
163 | export default connect(
164 | mapStateToProps,
165 | mapDispatchToProps
166 | )(WrappedRegistrationForm);
167 |
--------------------------------------------------------------------------------
/src/hoc/hoc.js:
--------------------------------------------------------------------------------
1 | const Hoc = props => props.children;
2 |
3 | export default Hoc;
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import registerServiceWorker from "./registerServiceWorker";
5 | import { createStore, compose, applyMiddleware, combineReducers } from "redux";
6 | import { Provider } from "react-redux";
7 | import thunk from "redux-thunk";
8 |
9 | import authReducer from "./store/reducers/auth";
10 |
11 | const composeEnhances = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
12 |
13 | const rootReducer = combineReducers({
14 | auth: authReducer
15 | });
16 |
17 | const store = createStore(rootReducer, composeEnhances(applyMiddleware(thunk)));
18 |
19 | const app = (
20 |
21 |
22 |
23 | );
24 |
25 | ReactDOM.render(app, document.getElementById("root"));
26 | registerServiceWorker();
27 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === "localhost" ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === "[::1]" ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener("load", () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | "This web app is being served cache-first by a service " +
44 | "worker. To learn more, visit https://goo.gl/SC7cgQ"
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === "installed") {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log("New content is available; please refresh.");
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log("Content is cached for offline use.");
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error("Error during service worker registration:", error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get("content-type").indexOf("javascript") === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | "No internet connection found. App is running in offline mode."
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ("serviceWorker" in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Route } from "react-router-dom";
3 | import Hoc from "./hoc/hoc";
4 |
5 | import Login from "./containers/Login";
6 | import Signup from "./containers/Signup";
7 | import Form from "./containers/Form";
8 | import InfiniteResults from "./containers/InfiniteScrollResults";
9 |
10 | const BaseRouter = () => (
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 | export default BaseRouter;
20 |
--------------------------------------------------------------------------------
/src/store/actions/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const AUTH_START = "AUTH_START";
2 | export const AUTH_SUCCESS = "AUTH_SUCCESS";
3 | export const AUTH_FAIL = "AUTH_FAIL";
4 | export const AUTH_LOGOUT = "AUTH_LOGOUT";
5 |
--------------------------------------------------------------------------------
/src/store/actions/auth.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import * as actionTypes from "./actionTypes";
3 |
4 | export const authStart = () => {
5 | return {
6 | type: actionTypes.AUTH_START
7 | };
8 | };
9 |
10 | export const authSuccess = token => {
11 | return {
12 | type: actionTypes.AUTH_SUCCESS,
13 | token: token
14 | };
15 | };
16 |
17 | export const authFail = error => {
18 | return {
19 | type: actionTypes.AUTH_FAIL,
20 | error: error
21 | };
22 | };
23 |
24 | export const logout = () => {
25 | localStorage.removeItem("token");
26 | localStorage.removeItem("expirationDate");
27 | return {
28 | type: actionTypes.AUTH_LOGOUT
29 | };
30 | };
31 |
32 | export const checkAuthTimeout = expirationTime => {
33 | return dispatch => {
34 | setTimeout(() => {
35 | dispatch(logout());
36 | }, expirationTime * 1000);
37 | };
38 | };
39 |
40 | export const authLogin = (username, password) => {
41 | return dispatch => {
42 | dispatch(authStart());
43 | axios
44 | .post("http://127.0.0.1:8000/rest-auth/login/", {
45 | username: username,
46 | password: password
47 | })
48 | .then(res => {
49 | const token = res.data.key;
50 | const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
51 | localStorage.setItem("token", token);
52 | localStorage.setItem("expirationDate", expirationDate);
53 | dispatch(authSuccess(token));
54 | dispatch(checkAuthTimeout(3600));
55 | })
56 | .catch(err => {
57 | dispatch(authFail(err));
58 | });
59 | };
60 | };
61 |
62 | export const authSignup = (username, email, password1, password2) => {
63 | return dispatch => {
64 | dispatch(authStart());
65 | axios
66 | .post("http://127.0.0.1:8000/rest-auth/registration/", {
67 | username: username,
68 | email: email,
69 | password1: password1,
70 | password2: password2
71 | })
72 | .then(res => {
73 | const token = res.data.key;
74 | const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
75 | localStorage.setItem("token", token);
76 | localStorage.setItem("expirationDate", expirationDate);
77 | dispatch(authSuccess(token));
78 | dispatch(checkAuthTimeout(3600));
79 | })
80 | .catch(err => {
81 | dispatch(authFail(err));
82 | });
83 | };
84 | };
85 |
86 | export const authCheckState = () => {
87 | return dispatch => {
88 | const token = localStorage.getItem("token");
89 | if (token === undefined) {
90 | dispatch(logout());
91 | } else {
92 | const expirationDate = new Date(localStorage.getItem("expirationDate"));
93 | if (expirationDate <= new Date()) {
94 | dispatch(logout());
95 | } else {
96 | dispatch(authSuccess(token));
97 | dispatch(
98 | checkAuthTimeout(
99 | (expirationDate.getTime() - new Date().getTime()) / 1000
100 | )
101 | );
102 | }
103 | }
104 | };
105 | };
106 |
--------------------------------------------------------------------------------
/src/store/reducers/auth.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from "../actions/actionTypes";
2 | import { updateObject } from "../utility";
3 |
4 | const initialState = {
5 | token: null,
6 | error: null,
7 | loading: false
8 | };
9 |
10 | const authStart = (state, action) => {
11 | return updateObject(state, {
12 | error: null,
13 | loading: true
14 | });
15 | };
16 |
17 | const authSuccess = (state, action) => {
18 | return updateObject(state, {
19 | token: action.token,
20 | error: null,
21 | loading: false
22 | });
23 | };
24 |
25 | const authFail = (state, action) => {
26 | return updateObject(state, {
27 | error: action.error,
28 | loading: false
29 | });
30 | };
31 |
32 | const authLogout = (state, action) => {
33 | return updateObject(state, {
34 | token: null
35 | });
36 | };
37 |
38 | const reducer = (state = initialState, action) => {
39 | switch (action.type) {
40 | case actionTypes.AUTH_START:
41 | return authStart(state, action);
42 | case actionTypes.AUTH_SUCCESS:
43 | return authSuccess(state, action);
44 | case actionTypes.AUTH_FAIL:
45 | return authFail(state, action);
46 | case actionTypes.AUTH_LOGOUT:
47 | return authLogout(state, action);
48 | default:
49 | return state;
50 | }
51 | };
52 |
53 | export default reducer;
54 |
--------------------------------------------------------------------------------
/src/store/utility.js:
--------------------------------------------------------------------------------
1 | export const updateObject = (oldObject, updatedProperties) => {
2 | return {
3 | ...oldObject,
4 | ...updatedProperties
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | .loader-div {
2 | text-align: center;
3 | border-radius: 4px;
4 | margin-bottom: 20px;
5 | padding: 30px 50px;
6 | margin: 20px 0;
7 | }
8 |
--------------------------------------------------------------------------------
/static/template.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin-top: 5rem;
3 | }
4 |
5 | ul {
6 | list-style-type: none;
7 | }
8 |
--------------------------------------------------------------------------------
/templates/bootstrap_form.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 |
6 |
7 |
8 | Filter Form
9 |
10 |
11 |
12 |
13 |
14 |
15 |
25 |
26 |
27 |
28 |
29 | Filter Journals
30 |
112 |
113 |
114 |
115 |
116 |
117 | {% for journal in queryset %}
118 |
119 | {{ journal.title }}
120 | Author: {{ journal.author.name }}
121 |
122 | {% for cat in journal.categories.all %}
123 | {{ cat }}
124 | {% endfor %}
125 |
126 | Publish date: {{ journal.publish_date }}
127 | View count: {{ journal.views }}
128 | Reviewed: {{ journal.reviewed }}
129 |
130 |
131 | {% endfor %}
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/djfilter/4c41cecad978d5a19be0803de7a245622a20e1a7/thumbnail.png
--------------------------------------------------------------------------------
/titles.txt:
--------------------------------------------------------------------------------
1 | U2 (disambiguation)
2 | Mount Temple Comprehensive School
3 | Sunday Bloody Sunday
4 | Pride (In the Name of Love)
5 | With or Without You
6 | I Still Haven't Found What I'm Looking For
7 | electronic dance music
8 | All That You Can't Leave Behind
9 | How to Dismantle an Atomic Bomb
10 | highest-grossing concert tour
11 | Songs of Experience
12 | best-selling music artists in history
13 | Rock and Roll Hall of Fame
14 | list of the "100 Greatest Artists of All Time"
15 | Amnesty International
16 | Paul Hewson ("Bono")
17 | David Evans ("the Edge")
18 | St. Fintan's High School
19 | Windmill Lane Studios
20 | 11 O'Clock Tick Tock
21 | Charismatic Christian
22 | Kid Creole and the Coconuts
23 | nuclear proliferation
24 | Red Rocks Amphitheatre
25 | Under a Blood Red Sky
26 | The Unforgettable Fire
27 | Martin Luther King, Jr.
28 | the Unforgettable Fire Tour
29 | Ethiopian famine relief
30 | platinum certification
31 | Where the Streets Have No Name
32 | The Joshua Tree Tour
33 | German reunification
34 | Grammy Award for Best Rock Performance by a Duo or Group with Vocal
35 | Grammy Award for Best Alternative Music Album
36 | Zoo TV: Live from Sydney
37 | Hold Me, Thrill Me, Kiss Me, Kill Me
38 | Original Soundtracks 1
39 | performed in Sarajevo on 23 September
40 | The Best of 1980–1990
41 | Stuck in a Moment You Can't Get Out Of
42 | September 11 attacks
43 | Madison Square Garden
44 | The Best of 1990–2000
45 | Sometimes You Can't Make It on Your Own
46 | City of Blinding Lights
47 | Louisiana Superdome
48 | The Saints Are Coming
49 | No Line on the Horizon
50 | Glastonbury Festival 2010
51 | Mandela: Long Walk to Freedom
52 | Golden Globe Award for Best Original Song
53 | Super Bowl television advertisement
54 | Universal Music Group
55 | The Washington Post
56 | Innocence + Experience Tour
57 | 13 November 2015 attacks
58 | Innocence + Experience: Live in Paris
59 | 2016 US presidential election
60 | The Joshua Tree Tour 2017
61 | tour marking the 30th anniversary of The Joshua Tree
62 | Bonnaroo Music Festival
63 | You're the Best Thing About Me
64 | Experience + Innocence Tour
65 | Mothers of the Disappeared
66 | forcibly disappeared
67 | country's civil war
68 | Running to Stand Still
69 | Siouxsie and the Banshees
70 | 1983–85 famine in Ethiopia
71 | Do They Know It's Christmas?
72 | A Conspiracy of Hope
73 | Salvadoran Civil War
74 | Good Friday Agreement
75 | Chernobyl Children's Project
76 | Brazilian president
77 | Make Poverty History
78 | Ambassador of Conscience Award
79 | 3rd iHeartRadio Music Awards
80 | solo soundtrack album
81 | She's a Mystery to Me
82 | Royal Shakespeare Company
83 | stage adaptation of A Clockwork Orange
84 | Put 'Em Under Pressure
85 | Irish national football team
86 | 1990 FIFA World Cup
87 | Theme from Mission: Impossible
88 | franchise's 1996 film
89 | Goddess in the Doorway
90 | William S. Burroughs
91 | Last Night on Earth
92 | Thanksgiving Prayer
93 | The Million Dollar Hotel
94 | The Ground Beneath Her Feet
95 | book of the same name
96 | Across the Universe
97 | Spider-Man: Turn Off the Dark
98 | List of awards and nominations received by U2
99 | best-selling albums in the US
100 | 22nd-highest-selling music artist in the US
101 | tied for the 14th-most of any artist
102 | tied for the 7th-most of any artist
103 | ranks 13th all-time
104 | Sunday Times Rich List 2013
105 | The 100 Greatest Artists of All Time
106 | 100 Greatest Songwriters of All Time
107 | The 500 Greatest Songs of All Time
108 | The 500 Greatest Albums of All Time
109 | The Huffington Post
110 | Best Rock Performance by a Duo or Group
111 | British Phonographic Industry
112 | American Music Award
113 | MTV Video Music Awards
114 | Golden Globe Awards
115 | List of U2 concert tours
116 | The Unforgettable Fire Tour
117 | Consequence of Sound
118 | The Philadelphia Inquirer
119 | Billboard-Hollywood Reporter Media Group
120 | Rocky Mountain News
121 | Rowman & Littlefield
122 | More spoken articles
123 | Wide Awake in America
124 | Please: PopHeart Live EP
125 | Live from Under the Brooklyn Bridge
126 | Wide Awake in Europe
127 | Melon: Remixes for Propaganda
128 | Hasta la Vista Baby! U2 Live from Mexico City
129 | Live from the Point Depot
130 | U2 Go Home: Live from Slane Castle, Ireland
131 | Medium, Rare & Remastered
132 | From the Ground Up: Edge's Picks from U2360°
133 | Two Hearts Beat as One
134 | When Love Comes to Town
135 | Even Better Than the Real Thing
136 | Who's Gonna Ride Your Wild Horses
137 | Stay (Faraway, So Close!)
138 | If God Will Send His Angels
139 | Window in the Skies
140 | The Ballad of Ronnie Drew
141 | I'll Go Crazy If I Don't Go Crazy Tonight
142 | The Miracle (of Joey Ramone)
143 | Every Breaking Wave
144 | Love Is Bigger Than Anything in Its Way
145 | U2 Live at Red Rocks: Under a Blood Red Sky
146 | The Unforgettable Fire Collection
147 | Achtung Baby: The Videos, the Cameos, and a Whole Lot of Interference from Zoo TV
148 | PopMart: Live from Mexico City
149 | Elevation 2001: Live from Boston
150 | Vertigo 2005: Live from Chicago
151 | Vertigo 05: Live from Milan
152 | U2360° at the Rose Bowl
153 | Vaillancourt Fountain concert
154 | concert in Sarajevo
155 | Super Bowl XXXVI halftime show
156 | Awards and nominations
157 | The Million Dollar Hotel (soundtrack)
158 | AHK-toong BAY-bi Covered
159 | Brit Award for International Group
160 | Huey Lewis and the News
161 | Red Hot Chili Peppers
162 | A Tribe Called Quest
163 | Strangers in the Night
164 | If Ever I Would Leave You
165 | The Windmills of Your Mind
166 | Alan and Marilyn Bergman
167 | Whistling Away the Dark
168 | Life Is What You Make It
169 | You Light Up My Life
170 | Arthur's Theme (Best That You Can Do)
171 | Flashdance... What a Feeling
172 | I Just Called to Say I Love You
173 | Take My Breath Away
174 | (I've Had) The Time of My Life
175 | Beauty and the Beast
176 | Streets of Philadelphia
177 | Can You Feel the Love Tonight
178 | Andrew Lloyd Webber
179 | My Heart Will Go On
180 | You'll Be in My Heart
181 | Things Have Changed
182 | The Hands That Built America
183 | Old Habits Die Hard
184 | A Love That Will Never Grow Old
185 | Gustavo Santaolalla
186 | The Song of the Heart
187 | Prince Rogers Nelson
188 | You Haven't Seen the Last of Me
189 | Writing's on the Wall
190 | Grammy Award for Album of the Year
191 | The Music from Peter Gunn
192 | Come Dance with Me!
193 | The Button-Down Mind of Bob Newhart
194 | Judy at Carnegie Hall
195 | The Barbra Streisand Album
196 | September of My Years
197 | A Man and His Music
198 | Sgt. Pepper's Lonely Hearts Club Band
199 | By the Time I Get to Phoenix
200 | Blood, Sweat & Tears
201 | Bridge over Troubled Water
202 | The Concert for Bangladesh
203 | Fulfillingness' First Finale
204 | Still Crazy After All These Years
205 | Songs in the Key of Life
206 | Saturday Night Fever
207 | Unforgettable... with Love
208 | The Miseducation of Lauryn Hill
209 | O Brother, Where Art Thou? Soundtrack
210 | Speakerboxxx/The Love Below
211 | Genius Loves Company
212 | Taking the Long Way
213 | River: The Joni Letters
214 | Random Access Memories
215 | Grammy Award for Record of the Year
216 | Nel Blu Dipinto Di Blu (Volare)
217 | Theme from A Summer Place
218 | I Left My Heart in San Francisco
219 | Days of Wine and Roses
220 | The Girl from Ipanema
221 | Aquarius/Let the Sunshine In
222 | The First Time Ever I Saw Your Face
223 | Killing Me Softly with His Song
224 | I Honestly Love You
225 | Love Will Keep Us Together
226 | Just the Way You Are
227 | What a Fool Believes
228 | The Doobie Brothers
229 | What's Love Got to Do with It
230 | Don't Worry, Be Happy
231 | Wind Beneath My Wings
232 | Another Day in Paradise
233 | I Will Always Love You
234 | Boulevard of Broken Dreams
235 | Billie Joe Armstrong
236 | Frank Edwin Wright III
237 | Not Ready to Make Nice
238 | Please Read the Letter
239 | Rolling in the Deep
240 | Somebody That I Used to Know
241 | James Honeyman-Scott
242 | WorldCat Identities
243 | Golden Globe Award-winning musicians
244 | Grammy Award winners
245 | Irish alternative rock groups
246 | Island Records artists
247 | Ivor Novello Award winners
248 | Juno Award for International Entertainer of the Year winners
249 | Mercury Records artists
250 | Musical groups established in 1976
251 | Musical groups from Dublin (city)
252 | Post-punk music groups
253 | Rock and Roll Hall of Fame inductees
254 | World Music Awards winners
255 | 1976 establishments in Ireland
--------------------------------------------------------------------------------