13 |
14 |
15 |
16 |
Load
17 |
Update
18 |
21 |
22 |
23 |
24 |
25 |
40 |
41 |
56 |
57 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/diag_project/settings.py:
--------------------------------------------------------------------------------
1 | import dj_database_url
2 | import os
3 |
4 |
5 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6 |
7 | ### heroku config:set SECRET_KEY=''whjwxt79_m_8impz71nc1@qdzzh99z(h%tkuvrkb8r8f4e''
8 | ### export SECRET_KEY='whjwxt79_m_8impz71nc1@qdzzh99z(h%tkuvrkb8r8f4e'
9 | SECRET_KEY = os.environ['SECRET_KEY']
10 |
11 | DEBUG = True
12 |
13 | ALLOWED_HOSTS = ['0.0.0.0', '127.0.0.1', 'pacific-depths-50874.herokuapp.com',
14 | 'bike-md.herokuapp.com']
15 |
16 | INSTALLED_APPS = [
17 | 'diag_app.apps.DiagAppConfig',
18 | 'django.contrib.admin',
19 | 'django.contrib.auth',
20 | 'django.contrib.contenttypes',
21 | 'django.contrib.sessions',
22 | 'django.contrib.messages',
23 | 'django.contrib.staticfiles',
24 | 'rest_framework',
25 | 'crispy_forms',
26 |
27 | ]
28 |
29 | MIDDLEWARE = [
30 | 'django.middleware.security.SecurityMiddleware',
31 | 'django.contrib.sessions.middleware.SessionMiddleware',
32 | 'django.middleware.common.CommonMiddleware',
33 | 'django.middleware.csrf.CsrfViewMiddleware',
34 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
35 | 'django.contrib.messages.middleware.MessageMiddleware',
36 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
37 | ]
38 |
39 | ROOT_URLCONF = 'diag_project.urls'
40 |
41 | REST_FRAMEWORK = {
42 | 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
43 | 'PAGE_SIZE': 500,
44 | 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
45 | }
46 |
47 | TEMPLATES = [
48 | {
49 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
50 | 'DIRS': [],
51 | 'APP_DIRS': True,
52 | 'OPTIONS': {
53 | 'context_processors': [
54 | 'django.template.context_processors.debug',
55 | 'django.template.context_processors.request',
56 | 'django.contrib.auth.context_processors.auth',
57 | 'django.contrib.messages.context_processors.messages',
58 | ],
59 | },
60 | },
61 | ]
62 |
63 | WSGI_APPLICATION = 'diag_project.wsgi.application'
64 |
65 |
66 | DATABASES = {
67 | 'default': {
68 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
69 | 'NAME': 'BIKEmD',
70 | 'USER': '',
71 | 'PASSWORD': '',
72 | 'HOST': 'localhost',
73 | 'PORT': '5432',
74 | }
75 | }
76 |
77 | db_from_env = dj_database_url.config(conn_max_age=500)
78 |
79 | # DATABASES['default'].update(db_from_env)
80 | #
81 | # DATABASES['default'] = dj_database_url.config()
82 |
83 |
84 | AUTH_PASSWORD_VALIDATORS = [
85 | {
86 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
87 | },
88 | {
89 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
90 | },
91 | {
92 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
93 | },
94 | {
95 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
96 | },
97 | ]
98 |
99 | LOGIN_REDIRECT_URL = '/diag_app/'
100 |
101 | LANGUAGE_CODE = 'en-us'
102 |
103 | TIME_ZONE = 'UTC'
104 |
105 | USE_I18N = True
106 |
107 | USE_L10N = True
108 |
109 | USE_TZ = True
110 |
111 | STATIC_URL = '/static/'
112 |
113 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
114 |
115 | STATIC_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles')
116 |
117 | STATIC_URL = '/static/'
118 |
119 | STATICFILES_DIRS = (
120 | os.path.join(PROJECT_ROOT, '../diag_app/static'),
121 | )
122 |
123 | STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
124 |
--------------------------------------------------------------------------------
/diag_app/templates/build_templates/problem_detail.html:
--------------------------------------------------------------------------------
1 |
2 | {%load static%}
3 |
4 |
5 |
6 |
Problem Detail
7 |
9 |
10 |
11 |
12 |
42 |
43 |
54 |
55 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/diag_app/static/css/_base.scss:
--------------------------------------------------------------------------------
1 | $charcoal: #313237;
2 | $seafoam: #a8bdba;
3 | $golden: #b68d58;
4 | $teal: #45756e;
5 |
6 | @mixin smallfont{
7 | font-size:1em;
8 | font-family:gill sans;
9 | text-transform:uppercase;
10 | }
11 |
12 | @mixin regularfont{
13 | font-size:1.5em;
14 | font-family:gill sans;
15 | text-transform:uppercase;
16 | }
17 |
18 | @mixin mediumfont{
19 | font-size:2.25em;
20 | font-family:gill sans;
21 | text-transform:uppercase;
22 | }
23 |
24 | @mixin largefont{
25 | font-size:3.75em;
26 | font-family:gill sans;
27 | text-transform:uppercase;
28 | }
29 |
30 | header {
31 | height: 58px;
32 | padding-left: 10%;
33 | padding-top: 1%;
34 | padding-bottom: 2%; }
35 | header .templatelogo {
36 | width: 15%;
37 | float: left;
38 | margin-right: 4%;
39 | margin-top: 1.5%; }
40 |
41 | header .templatenav {
42 | display: inline; }
43 | header .templatenav li {
44 | list-style-type: none;
45 | display: inline-block;
46 | margin-left: 2%;
47 | float: left; }
48 | header .templatenav li a {
49 | text-decoration: none;
50 | color: white;
51 | font-size: 1.5rem;
52 | font-family: gill sans;
53 | text-transform: uppercase;
54 | font-weight: normal;
55 | text-align: right; }
56 | header .templatenav li a:after {
57 | display: block;
58 | position: absolute;
59 | bottom: 30px;
60 | height: 5px;
61 | width: 0px;
62 | background-color: #a8bdba;
63 | transition: width .5s;
64 | content: ""; }
65 | header .templatenav li .link1:hover:after {
66 | width: 100%; }
67 | header .templatenav li .link2:hover:after {
68 | width: 100%; }
69 | header .templatenav li .link3:hover:after {
70 | width: 100%; }
71 |
72 | .dropdown {
73 | margin-top: 2%;
74 | display: none;
75 | padding: 5%;
76 | position: absolute;
77 | top: 500px; }
78 |
79 | header .templatenav .dropdown li a {
80 | font-size: 1.1rem; }
81 |
82 | header .templatenav .dropdown li a:hover {
83 | color: #ba973a; }
84 |
85 | .droplist {
86 | display: block;
87 | clear: both;
88 | width: 100%; }
89 |
90 | .droplist:hover {
91 | transition: .5s; }
92 |
93 | #searchThis {
94 | height: 30px;
95 | margin-bottom: 1.5rem;
96 | text-align: center;
97 | text-transform: uppercase;
98 | width: 20% !important; }
99 |
100 | #searchBox {
101 | appearance: none;
102 | -moz-appearance: none;
103 | -webkit-appearance: none;
104 | border: 0px; }
105 |
106 | #searchButton {
107 | background-color: rgba(255, 255, 255, 0.55);
108 | border: 0px;
109 | width: 40px; }
110 |
111 | #searchButton:hover {
112 | background-color: #a8bdba;
113 | border: 0px;
114 | transition: .5s; }
115 |
116 | .templatenav{
117 |
118 | li{
119 | display:inline-block;
120 | padding:2% .75%;
121 | margin:0;
122 | float:left;
123 |
124 | a{
125 | text-decoration:none;
126 | color:white;
127 | @include smallfont;
128 | font-size: 1.25rem;
129 | position:relative;
130 | }
131 |
132 | }
133 |
134 | a:after{
135 | display:block;
136 | position:absolute;
137 | bottom:30px;
138 | height:5px;
139 | width:0px;
140 | background-color:$golden;
141 | transition:width .5s;
142 | content:"";
143 | z-index:-1;
144 | }
145 |
146 | .link1:hover:after{
147 | width:100%;
148 | }
149 |
150 | .link2:hover:after{
151 | width:100%;
152 | }
153 |
154 | .link3:hover:after{
155 | width:100%;
156 | }
157 | }
158 | }
159 | }
160 |
161 |
162 | footer{
163 | background-color:rgba(white, .25);
164 | height:30px;
165 | position:relative;
166 | top:540px;
167 | p{
168 | color:white;
169 | text-align:center;
170 | padding-top:.5%;
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/diag_app/static/js/problem_list.js:
--------------------------------------------------------------------------------
1 |
2 | //get url for the ajax call
3 | var url = window.location.href;
4 | //kick off ajax
5 | getModel(url);
6 | getProblem(url);
7 | ////////////////////////////////////////////////////////////////////////////////
8 | // Helper: getProblem
9 | // parameters: URL
10 | // description: Ajax call for problem and it's solutions.
11 | // return: none
12 | ////////////////////////////////////////////////////////////////////////////////
13 | function getProblem(url) {
14 | var id = url.split('/');
15 | var model = id[4];
16 | var id = id[5];
17 | var url = '/api/get-problems?system=' + id + '&model=' + model;
18 | $.ajax({
19 | url: url,
20 | type: 'GET',
21 | }).done(function(results) {
22 | var problems = results.results;
23 | if (problems.length > 0) {
24 | var source = $('#problem-list-template').html();
25 | var template = Handlebars.compile(source);
26 | var html = template(problems);
27 | var systemKey = problems[0].system;
28 | var context = {
29 | problem: problems,
30 | system: systemKey,
31 | };
32 | var source = $('#problem-list-template').html();
33 | var template = Handlebars.compile(source);
34 | var html = template(context);
35 | $('#problemList').append(html);
36 | } else {
37 | $('#problemList').append("
No problem was found about this system! ");
38 | }
39 | })
40 | }
41 | ////////////////////////////////////////////////////////////////////////////////
42 | // Helper: getModel
43 | // parameters: URL
44 | // description: Ajax call for bike model.
45 | // return: none
46 | ////////////////////////////////////////////////////////////////////////////////
47 | function getModel(url) {
48 | let splited_url = url.split('/');
49 | let model = splited_url[4];
50 |
51 | $.ajax({
52 | url: '/api/models/' + model,
53 | type: 'GET',
54 | }).done(function(results){
55 | var source2 = $('#model-template').html();
56 | var template2 = Handlebars.compile(source2);
57 | var html2 = template2(results);
58 | $('#model').append(html2);
59 | })
60 | }
61 | ////////////////////////////////////////////////////////////////////////////////
62 | // Helper: formatTime
63 | // parameters: timestamp
64 | // description: Formats timestamps to make them pretty and more human readable.
65 | // return: String of month/day/year
66 | ////////////////////////////////////////////////////////////////////////////////
67 | Handlebars.registerHelper('formatTime', function (posted) {
68 | var time = posted.replace('T', ':');
69 | var date = time.split(":")[0];
70 | var year = Number(date.split("-")[0]);
71 | var month = Number(date.split("-")[1]);
72 | var day = Number(date.split("-")[2]);
73 | var months = {
74 | "January": 1,
75 | "February ": 2,
76 | "March": 3,
77 | "April": 4,
78 | "May": 5,
79 | "June": 6,
80 | "July": 7,
81 | "August": 8,
82 | "September": 9,
83 | "October": 10,
84 | "November": 11,
85 | "December": 12,
86 | };
87 | for (var i in months) {
88 | if (month === months[i]) {
89 | month = i;
90 | }
91 | }
92 | return month + " " + day + " " + year;
93 | })
94 | ////////////////////////////////////////////////////////////////////////////////
95 | // Helper: linkURLModel
96 | // parameters: model object
97 | // description: Formats URL to link to detail page.
98 | // return: String of URL for bike's detail page
99 | ////////////////////////////////////////////////////////////////////////////////
100 | Handlebars.registerHelper('linkURLModel', function (object) {
101 | id = Handlebars.Utils.escapeExpression(object.id)
102 | name = Handlebars.Utils.escapeExpression(object.name)
103 | url = '/model_detail/' + id
104 | return '
' + '' + name + ' ' + ' '
105 |
106 | })
107 |
--------------------------------------------------------------------------------
/diag_project/settings_example.py:
--------------------------------------------------------------------------------
1 | import dj_database_url
2 | import os
3 |
4 |
5 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6 | DEBUG = True
7 |
8 | ### SECRET KEY ###
9 | #secret can be set as an environment varible in your shell or here:
10 |
11 | ### For Shell ###
12 | #un-comment this line
13 | #SECRET_KEY = os.environ['SECRET_KEY']
14 | ### Run this command in your terminal ###
15 | #export SECRET_KEY='whjwxt79_m_8impz71nc1@qdzzh99z(h%tkuvrkb8r8f4e'
16 |
17 | ### For setting the key here just un-comment the following line ###
18 | #SECRET_key = 'whjwxt79_m_8impz71nc1@qdzzh99z(h%tkuvrkb8r8f4e'
19 |
20 | #secret keys can be generated at http://www.miniwebtool.com/django-secret-key-generator/
21 | ############
22 |
23 | ### Needed for local development ###
24 | ALLOWED_HOSTS = ['0.0.0.0', '127.0.0.1']
25 |
26 | INSTALLED_APPS = [
27 | 'diag_app.apps.DiagAppConfig',
28 | 'django.contrib.admin',
29 | 'django.contrib.auth',
30 | 'django.contrib.contenttypes',
31 | 'django.contrib.sessions',
32 | 'django.contrib.messages',
33 | 'django.contrib.staticfiles',
34 | 'rest_framework',
35 | 'crispy_forms',
36 | ]
37 |
38 | MIDDLEWARE = [
39 | 'django.middleware.security.SecurityMiddleware',
40 | 'django.contrib.sessions.middleware.SessionMiddleware',
41 | 'django.middleware.common.CommonMiddleware',
42 | 'django.middleware.csrf.CsrfViewMiddleware',
43 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
44 | 'django.contrib.messages.middleware.MessageMiddleware',
45 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
46 | ]
47 |
48 | ROOT_URLCONF = 'diag_project.urls'
49 |
50 | REST_FRAMEWORK = {
51 | 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
52 | 'PAGE_SIZE': 500,
53 | 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
54 | }
55 |
56 | TEMPLATES = [
57 | {
58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
59 | 'DIRS': [],
60 | 'APP_DIRS': True,
61 | 'OPTIONS': {
62 | 'context_processors': [
63 | 'django.template.context_processors.debug',
64 | 'django.template.context_processors.request',
65 | 'django.contrib.auth.context_processors.auth',
66 | 'django.contrib.messages.context_processors.messages',
67 | ],
68 | },
69 | },
70 | ]
71 |
72 | WSGI_APPLICATION = 'diag_project.wsgi.application'
73 |
74 | #create postgreSQL DB with the name BIKEMD in order use this preset connection.
75 | DATABASES = {
76 | 'default': {
77 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
78 | 'NAME': 'BIKEMD',
79 | 'USER': '',
80 | 'PASSWORD': '',
81 | 'HOST': 'localhost',
82 | 'PORT': '5432',
83 | }
84 | }
85 |
86 | ### Needed for deployment to heroku.###
87 | # db_from_env = dj_database_url.config(conn_max_age=500)
88 | # DATABASES['default'].update(db_from_env)
89 | # DATABASES['default'] = dj_database_url.config()
90 |
91 |
92 | AUTH_PASSWORD_VALIDATORS = [
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
95 | },
96 | {
97 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
98 | },
99 | {
100 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
101 | },
102 | {
103 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
104 | },
105 | ]
106 |
107 | LOGIN_REDIRECT_URL = '/diag_app/'
108 |
109 | LANGUAGE_CODE = 'en-us'
110 |
111 | TIME_ZONE = 'UTC'
112 |
113 | USE_I18N = True
114 |
115 | USE_L10N = True
116 |
117 | USE_TZ = True
118 |
119 | STATIC_URL = '/static/'
120 |
121 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
122 |
123 | STATIC_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles')
124 |
125 | STATIC_URL = '/static/'
126 |
127 | STATICFILES_DIRS = (
128 | os.path.join(PROJECT_ROOT, '../diag_app/static'),
129 | )
130 |
131 | STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
132 |
--------------------------------------------------------------------------------
/diag_app/templates/problem_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {%load static%}
3 |
4 | {% block titlecontent %}
5 |
Problem Detail
6 | {% endblock %}
7 |
8 | {% block bodycontent %}
9 |
10 | {% endblock %}
11 |
12 | {%block content%}
13 |
42 |
43 |
44 |
56 |
57 |
80 | {% endblock %}
81 |
82 | {% block jscontent %}
83 |
84 |
85 | {% endblock %}
86 |
87 |
--------------------------------------------------------------------------------
/diag_app/serializers.py:
--------------------------------------------------------------------------------
1 | from .models import Vote, Problem, Solution, Tech, Rating, System, Brand
2 | from .models import Model, Problem_Model, Commit, Notification
3 | from django.contrib.auth.models import User
4 | from rest_framework import serializers
5 | from django.contrib.auth import login
6 |
7 |
8 | class SystemSerializer(serializers.ModelSerializer):
9 |
10 | class Meta:
11 | model = System
12 | fields = ['id', 'name', 'url']
13 |
14 |
15 | class UserSerializer(serializers.ModelSerializer):
16 | password = serializers.CharField(write_only=True)
17 |
18 | def create(self, validated_data):
19 | user = User.objects.create(
20 | username=validated_data['username'],
21 | email=validated_data['email']
22 | )
23 | user.set_password(validated_data['password'])
24 | user.save()
25 | return user
26 |
27 | class Meta:
28 | model = User
29 | fields = ['id', 'password', 'email', 'username']
30 | read_only_fields = ['is_staff', 'is_superuser', 'is_active',
31 | 'date_joined',]
32 |
33 |
34 | class TechGetSerializer(serializers.ModelSerializer):
35 | user = UserSerializer(many=False, read_only=True)
36 |
37 | class Meta:
38 | model = Tech
39 | fields = ['id', 'experience', 'job_title', 'shop', 'user',
40 | 'tech_rating']
41 |
42 |
43 | class TechPostSerializer(serializers.ModelSerializer):
44 |
45 | class Meta:
46 | model = Tech
47 | fields = ['id', 'experience', 'job_title', 'shop', 'user',
48 | 'tech_rating']
49 |
50 |
51 |
52 | class CommitSerializer(serializers.ModelSerializer):
53 |
54 | class Meta:
55 | model = Commit
56 | fields = ['id', 'solution', 'tech', 'posted', 'text', 'url']
57 |
58 |
59 | class VoteSerializer(serializers.ModelSerializer):
60 |
61 | class Meta:
62 | model = Vote
63 | fields = "__all__"
64 |
65 |
66 | class SolutionGetSerializer(serializers.ModelSerializer):
67 | votes = VoteSerializer(many=True, read_only=True)
68 | commits = CommitSerializer(many=True, read_only=True)
69 | tech = TechGetSerializer(many=False, read_only=True)
70 |
71 | class Meta:
72 | model = Solution
73 | fields = ['id', 'description', 'time_required', 'parts_cost',
74 | 'problem', 'tech', 'posted', 'score', 'commits',
75 | 'votes', 'url']
76 |
77 |
78 | class SolutionPostSerializer(serializers.ModelSerializer):
79 |
80 | class Meta:
81 | model = Solution
82 | fields = ['id', 'description', 'time_required', 'parts_cost',
83 | 'problem', 'tech', 'posted', 'score', 'url']
84 |
85 |
86 | class ProblemGetSerializer(serializers.ModelSerializer):
87 | solutions = SolutionGetSerializer(many=True, read_only=True)
88 | tech = TechGetSerializer(many=False, read_only=True)
89 | system = SystemSerializer(many=False, read_only=True)
90 |
91 | class Meta:
92 | model = Problem
93 | fields = ['id', 'title', 'system', 'description', 'tech',
94 | 'model', 'posted', 'url', 'solutions']
95 |
96 |
97 | class ProblemPostSerializer(serializers.ModelSerializer):
98 |
99 | class Meta:
100 | model = Problem
101 | fields = ['id', 'title', 'system', 'description', 'tech',
102 | 'model', 'posted', 'url']
103 |
104 |
105 | class RatingSerializer(serializers.ModelSerializer):
106 |
107 | class Meta:
108 | model = Rating
109 | fields = "__all__"
110 |
111 |
112 | class BrandSerializer(serializers.ModelSerializer):
113 |
114 | class Meta:
115 | model = Brand
116 | fields = ['id', 'name', 'url']
117 |
118 |
119 | class ModelSerializer(serializers.ModelSerializer):
120 | brand = BrandSerializer(many=False, read_only=True)
121 |
122 | class Meta:
123 | model = Model
124 | fields = ['id', 'name', 'brand', 'year', 'url']
125 |
126 |
127 | class NotificationSerializer(serializers.ModelSerializer):
128 |
129 | class Meta:
130 | model = Notification
131 | fields = ['id', 'tech', 'message', 'posted', 'solution', 'commit']
132 |
--------------------------------------------------------------------------------
/diag_app/static/css/_profile.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 | @import "mixins";
3 |
4 | .userprofilebody {
5 | background-image:
6 | linear-gradient(
7 | rgba($charcoal, 0.5),
8 | rgba($charcoal, 0.5)
9 | ),
10 | url("../images/index-background.jpg");
11 | background-repeat: no-repeat;
12 | background-attachment: fixed;
13 | background-size: cover;
14 | margin:0;
15 | }
16 | //Title background
17 | .profile-details-goldendecor {
18 | background-color: $golden;
19 | width: 70%;
20 | padding-top: 1%;
21 | height: 70px;
22 | margin-left: 15%;
23 | margin-top: 7%;
24 | margin-right: 15%;
25 | }
26 | //Page Title
27 | .profile-title {
28 | @include mediumfont;
29 | color: white;
30 | font-weight: 400;
31 | margin-left: 10%;
32 | margin-top: 2%;
33 | margin-bottom: -2%;
34 | font-weight: 200;
35 | padding-top: 1%;
36 | padding-bottom: 1%;
37 | hr {
38 | margin-top: 0;
39 | margin-bottom: 0;
40 | margin-left: 5%;
41 | }
42 | }
43 | //Container for the profile page
44 | .profile-details-container {
45 | height: auto;
46 | width:70%;
47 | background-color: white;
48 | padding-top: 2%;
49 | padding-bottom: 2%;
50 | box-sizing:border-box;
51 | margin: 0 auto;
52 | overflow:hidden;
53 | }
54 |
55 | .photo-box {
56 | // width: 30%;
57 | height: 220px;
58 | float: left;
59 | margin-top: 2%;
60 | margin-left: 2%;
61 | margin-bottom: 1%;
62 | padding:1%;
63 | background-color: white;
64 |
65 | .profile-photo img {
66 | margin: 0 auto;
67 | }
68 | }
69 | .profile-photo {
70 |
71 | }
72 | .profile-page-information-area {
73 | font-size: 1rem;
74 | font-weight: 400;
75 | margin-top: .5%;
76 | box-sizing:border-box;
77 | height: 210px;
78 | }
79 | .profile-information-details {
80 | width: 52%;
81 | float: right;
82 | background-color: white;
83 | margin-left: 1%;
84 | margin-right: 2%;
85 | margin-top: 2%;
86 | padding: 2%;
87 | overflow: scroll;
88 | h4 {
89 | margin-top: 1%;
90 | margin-bottom: 1%;
91 | }
92 | p {
93 | @include smallfont;
94 | background-color: $golden;
95 | color: white;
96 | text-align: center;
97 | margin-top: 1px;
98 | }
99 | }
100 | .profile-page-username {
101 | @include regularfont;
102 | color: $charcoal;
103 | font-weight: 200;
104 | margin-top: 1%;
105 | margin-bottom: 1%;
106 | }
107 |
108 | .modaltext {
109 | @include smallfont;
110 | color: $charcoal;
111 | font-weight: 200;
112 | }
113 | .post-container {
114 | @include smallfont;
115 | color: $charcoal;
116 | margin: 0;
117 | }
118 |
119 | .profile-box {
120 | width: 30%;
121 | height: 220px;
122 | float: left;
123 | margin-top: 2%;
124 | margin-left: 2%;
125 | margin-bottom: 1%;
126 | background-color: white;
127 | position: relative;
128 | }
129 | .profile-questions-asked {
130 | width: 92%;
131 | height: 220px;
132 | float: right;
133 | background-color: #ffffff;
134 | margin-right: 2%;
135 | margin-top: 2%;
136 | padding: 2%;
137 | overflow: scroll;
138 | p {
139 | @include smallfont;
140 | background-color: $seafoam;
141 | color: white;
142 | text-align: center;
143 | margin-top: 1px;
144 | padding: 2%;
145 | }
146 | }
147 | .techAnswers {
148 |
149 | }
150 | .profile-questions-answered {
151 | width: 92%;
152 | height: 220px;
153 | float: right;
154 | background-color: #ffffff;
155 | margin-right: 2%;
156 | margin-bottom: 1%;
157 | padding: 2%;
158 | overflow: scroll;
159 | p {
160 | @include smallfont;
161 | background-color: $seafoam;
162 | color: white;
163 | text-align: center;
164 | margin-top: 1px;
165 | padding: 2%;
166 | }
167 | }
168 | .profile-techQuestions {
169 |
170 | }
171 | .exitbutton {
172 | height:40px;
173 | width:100%;
174 | box-sizing:border-box;
175 | @include smallfont;
176 | float: right;
177 | text-align: right;
178 | margin-right: 15%;
179 | margin-top: 1%;
180 | a{
181 | text-decoration:none;
182 | color:$charcoal;
183 | padding:.5%;
184 | background-color:$seafoam;
185 | }
186 | a:hover{
187 | color:$seafoam;
188 | background-color:$charcoal;
189 | }
190 | }
191 |
192 | h4.tech-rating {
193 | margin-top: 1%;
194 | margin-bottom: 1%;
195 | }
196 |
--------------------------------------------------------------------------------
/diag_app/templates/main.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {%load static%}
3 |
4 | {% block titlecontent %}
5 |
main
6 | {% endblock %}
7 |
8 | {% block bodycontent %}
9 |
10 | {% endblock %}
11 | {%block content%}
12 |
Diagnose
13 |
14 |
15 |
16 |
19 |
20 | {% block footercontent %}
21 |
22 | {% endblock %}
23 |
24 |
25 |
43 |
44 |
45 |
73 |
74 |
75 |
106 | {% endblock %}
107 |
108 | {% block jscontent %}
109 |
110 | {% endblock %}
111 |
112 |
--------------------------------------------------------------------------------
/diag_app/static/css/_problem-detail.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 | @import "mixins";
3 |
4 | h5{
5 | margin:0;
6 | }
7 |
8 | .prob-detailbody{
9 | background-image:url("../images/bikedetails-background.jpg");
10 | background-size:cover;
11 | background-attachment:fixed;
12 | margin:0;
13 | }
14 |
15 | .prob-form-background{
16 | height:auto;
17 | width:80%;
18 | margin:0 auto;
19 | // padding:2%;
20 |
21 | .prob-header, .sol-header, .sol-name{
22 | @include mediumfont;
23 | color:white;
24 | background-color:$charcoal;
25 | text-align:center;
26 | margin:0;
27 | padding:.5% 0;
28 | }
29 |
30 | .sol-header, .sol-name{
31 | @include smallregularfont;
32 | background-color:#ccc;
33 | color:$charcoal;
34 | }
35 |
36 | .prob-title{
37 | margin:0;
38 | }
39 |
40 | .posted-by{
41 | display:inline-block;
42 | margin-bottom:10px;
43 | }
44 |
45 | .tech-rating{
46 | display:inline-block;
47 | border-left:1px solid black;
48 | border-right:1px solid black;
49 | padding:0 .5%;
50 | }
51 |
52 | .date-time{
53 | display:inline-block;
54 | color:$teal;
55 | margin:0;
56 | font-weight:500;
57 | }
58 |
59 | .solution-score{
60 | display:inline-block;
61 | }
62 |
63 | .solution-time, .solution-cost{
64 | float:right;
65 | padding:0 1%;
66 | }
67 |
68 | .timecost-value{
69 | color:$teal;
70 | }
71 |
72 | .describe-title{
73 | @include regularfont;
74 | color:$charcoal;
75 | text-align:center;
76 | font-weight: 400;
77 | margin-top: 7%;
78 | }
79 |
80 | .form-text{
81 | text-align:center;
82 | @include smallfont;
83 | font-weight: 400;
84 | margin: 1%;
85 | }
86 |
87 | .counterSolution{
88 | text-align: right;
89 | margin-right: 10%;
90 | margin-top: 1%;
91 | }
92 |
93 |
94 | .prob-box, .sol-form-container{
95 | height:auto;
96 | background-color:rgba(white, 1);
97 | margin:0 auto;
98 | padding:2%;
99 | box-sizing:border-box;
100 | }
101 |
102 | .sol-box{
103 | height:auto;
104 | background-color:rgba(white, 1);
105 | margin:0 auto;
106 | border-bottom:.5px solid #ccc;
107 | padding:2%;
108 | box-sizing:border-box;
109 | }
110 |
111 | .arrow{
112 | color:white;
113 | cursor:pointer;
114 | }
115 |
116 | .vote{
117 | border:0;
118 | border-radius:100%;
119 | background-color:$charcoal;
120 | }
121 |
122 | .solution-score{
123 | margin-top:30px;
124 | }
125 |
126 | .time-input{
127 | display:inline-block;
128 | height:20px;
129 | width:52%;
130 | text-align:center;
131 | margin-left:20%;
132 | }
133 |
134 | .cost-input{
135 | display:inline-block;
136 | height:20px;
137 | width:50%;
138 | text-align:center;
139 | }
140 |
141 | .dollar, .hrs{
142 | @include smallfont;
143 | color:$charcoal;
144 | display:inline-block;
145 | margin-top:1%;
146 | }
147 |
148 | .hrs{
149 | text-transform:lowercase;
150 | }
151 |
152 | .dollar{
153 | margin-left:23%;
154 | }
155 |
156 | .solution-box{
157 | display:block;
158 | margin:0 auto;
159 | width:80%;
160 | height:auto;
161 | }
162 |
163 | .solution-text{
164 | font-weight:500;
165 | text-transform:none;
166 | font-size:1rem;
167 | }
168 |
169 | .solution-submit{
170 | color:white;
171 | background-color:$charcoal;
172 | @include smallfont;
173 | padding:2%;
174 | display:block;
175 | margin:0 auto;
176 | // margin-bottom: 2%;
177 | border:0px;
178 | box-sizing:border-box;
179 | cursor: pointer;
180 | }
181 |
182 | .solution-submit:hover{
183 | background-color:white;
184 | color:$charcoal;
185 | border:1px solid $charcoal;
186 | transition: .5s;
187 | }
188 |
189 | .time-box {
190 | float: left;
191 | width: 50%;
192 | }
193 | .cost-box {
194 | float: right;
195 | width: 50%;
196 | }
197 |
198 | .prob-title {
199 | @include regularfont;
200 | font-weight: 600;
201 | }
202 | .posted-by {
203 | @include smallfont;
204 | font-weight: 600;
205 | }
206 | .question-text {
207 | @include smallfont;
208 | font-weight: 400;
209 | text-transform:none;
210 | }
211 | .prob-form {
212 | margin-left: 10%;
213 | margin-right: 10%;
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/diag_app/static/js/main.js:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | FILE SUMMARY
3 | At base these functions all preform ajax calls in order to populate
4 | Handlebars templates. After showBrands is kicked off the rest are triggered
5 | by onclicks. Obviously this is outdated and sub par and needs to be refactored.
6 | Also, one selection box should not require a seperate function for each step.
7 | This same functionality will also need to be addressed in the model selection
8 | for the problem posting modal.
9 | *******************************************************************************/
10 | //////////////////////////////////////////////////////////////////
11 | // Set up Dom
12 | //////////////////////////////////////////////////////////////////
13 | showBrands();
14 | //////////////////////////////////////////////////////////////////
15 | // function: showBrands
16 | // parameters: none
17 | // description: This function loads the brands currently in the DB
18 | // This is first step of the model selection box
19 | // return: none
20 | //////////////////////////////////////////////////////////////////
21 | function showBrands() {
22 | $.ajax({
23 | url: '/api/brands/',
24 | type: 'GET',
25 | }).done(function(results) {
26 | var source = $('#brand-template').html();
27 | var template = Handlebars.compile(source);
28 | var html = template(results.results);
29 | $('#listing').empty();
30 | $('#listing').append(html);
31 | $('#brandStep').toggleClass('selected');
32 | $('#brand-number').toggleClass('selected-number');
33 | });
34 | }
35 | //////////////////////////////////////////////////////////////////////////
36 | // function: showYears
37 | // parameters: none
38 | // description: This function loads the years for the brand selected.
39 | // This is the second step of the model selection box
40 | // return: none
41 | //////////////////////////////////////////////////////////////////////////
42 | function showYears(id) {
43 | $.ajax({
44 | url: '/api/models?brand=' + id,
45 | type: 'GET'
46 | }).done(function(results) {
47 | $('#listing').empty();
48 | var bike = results.results;
49 | var years = [];
50 | for (var i=0; i < bike.length; i++) {
51 | if (!(years.includes(bike[i].year))) {
52 | years.push(bike[i].year);
53 | }
54 | }
55 | years.sort(function(a, b){return b-a});
56 | var context = {
57 | modelYears : years,
58 | brandId : id,
59 | }
60 | var source = $('#year-template').html();
61 | var template = Handlebars.compile(source);
62 | var html = template(context);
63 | $('#listing').empty();
64 | $('#listing').append(html);
65 | $('#yearStep').toggleClass('selected');
66 | $('#year-number').toggleClass('selected-number');
67 | });
68 | }
69 | //////////////////////////////////////////////////////////////////////////
70 | // function: showModels
71 | // parameters: none
72 | // description: This function loads the models for the brand/year selected.
73 | // This is the final step of the model selection box
74 | // return: none
75 | //////////////////////////////////////////////////////////////////////////
76 | function showModels(year) {
77 | var brandId = $("#brandId").val();
78 | $.ajax({
79 | url: '/api/models?brand=' + brandId + '&year=' + year,
80 | type: 'GET'
81 | }).done(function(results) {
82 | var brandID = results.results[0].brand.id;
83 | var bikeList = results.results;
84 | var context = {
85 | brand: brandID,
86 | bikes: bikeList
87 | }
88 | var source = $('#model-template').html();
89 | var template = Handlebars.compile(source);
90 | var html = template(context);
91 | $('#listing').empty();
92 | $('#listing').append(html);
93 | $('#modelStep').toggleClass('selected');
94 | $('#model-number').toggleClass('selected-number');
95 | });
96 | }
97 | //////////////////////////////////////////////////////////////////////////
98 | // function: linkBike
99 | // parameters: none
100 | // description: This function loads the model page for the model selected.
101 | // return: none
102 | //////////////////////////////////////////////////////////////////////////
103 | function linkBike (id) {
104 | url = '/model_detail/' + id;
105 | window.location = url;
106 | }
107 |
--------------------------------------------------------------------------------
/diag_app/static/css/style.scss:
--------------------------------------------------------------------------------
1 | @import "index.scss";
2 | @import "createaccount.scss";
3 | @import "main.scss";
4 | @import "bikedetails.scss";
5 | @import "problem-detail.scss";
6 | @import "profile.scss";
7 | @import "system-problem-list.scss";
8 | @import "problem-list.scss";
9 | @import "aboutus.scss";
10 | @import "variables.scss";
11 | @import "mixins.scss";
12 |
13 |
14 | body {
15 | margin:0;
16 | overflow:auto;
17 | }
18 |
19 | header{
20 | height:58px;
21 | padding-left: 10%;
22 | padding-top: .5%;
23 | padding-bottom: 5%;
24 | margin-top:5%;
25 |
26 | .templatelogo{
27 | width:18%;
28 | float:left;
29 | margin-top: 1.5%;
30 | padding-right:.9%;
31 | }
32 |
33 | .templatenav{
34 |
35 | .search-form{
36 | height:30px;
37 | margin-bottom:1.5rem;
38 | text-align:center;
39 | margin-bottom:1%;
40 | }
41 |
42 | .search-field{
43 | appearance:none;
44 | -moz-appearance:none;
45 | -webkit-appearance:none;
46 | border: 0px;
47 | height: 21px;
48 | width: 125px;
49 | }
50 |
51 | .search-submit{
52 | background-color:$charcoal;
53 | color:white;
54 | border: 0px;
55 | height: 24px;
56 | @include smallfont;
57 | }
58 |
59 | .search-submit:hover{
60 | background-color:white;
61 | color:$charcoal;
62 | transition: .5s;
63 | cursor:pointer;
64 | }
65 |
66 | li{
67 | list-style-type:none;
68 | display:inline-block;
69 | padding: 2%;
70 | float:left;
71 | margin-top: 47px;
72 |
73 | a{
74 | position:relative;
75 | text-decoration:none;
76 | color:white;
77 | @include smallregularfont;
78 | }
79 |
80 | // hover state overline
81 | a:after{
82 | display:block;
83 | position:absolute;
84 | bottom:25px;
85 | height:3px;
86 | width:0px;
87 | background-color:$teal;
88 | transition:width .5s;
89 | content:"";
90 | }
91 |
92 | .link1:hover:after, .link2:hover:after, .link3:hover:after, .link4:hover:after{
93 | width:100%;
94 | }
95 | }
96 | }
97 | }
98 | // end hover state overline
99 |
100 | // my account dropdown menu
101 | .dropdown{
102 | display:none;
103 | padding:0;
104 | position:absolute;
105 | top: 400px;
106 |
107 | .droplist{
108 | display:block;
109 | clear:both;
110 | width: 100%;
111 | margin-top: 40px;
112 |
113 | a {
114 | @include smallfont;
115 | }
116 |
117 | // a:hover {
118 | // color: $teal;
119 | // transition: .5s;
120 | // }
121 | }
122 | }
123 |
124 | // title of pages
125 | .page-title {
126 | @include mediumfont;
127 | font-weight:500;
128 | color:white;
129 | text-align:left;
130 | margin-left: 10%;
131 | margin-bottom:0;
132 | margin-top:3%;
133 | }
134 |
135 |
136 | // modal
137 | .remodal{
138 | height:auto;
139 |
140 | textarea {
141 | width: 90%;
142 | }
143 | }
144 |
145 | .systems-dropdown{
146 | display:inline-block;
147 | }
148 |
149 | .modaltext{
150 | @include smallregularfont;
151 | margin-bottom: 1%;
152 | font-weight: 400;
153 | margin-top: 2%;
154 | }
155 |
156 | .modaltext-1{
157 | @include smallfont;
158 | font-weight: 200;
159 | margin: 0;
160 | margin-bottom: 10%;
161 | }
162 |
163 | .problemtitle-field{
164 | height:30px;
165 | width:90%;
166 | text-align:left;
167 | font-size:1rem;
168 | font-family:gill sans;
169 | color:$charcoal;
170 | }
171 |
172 | .submit-button{
173 | color:white;
174 | background-color:$charcoal;
175 | color:white;
176 | @include smallfont;
177 | padding:2%;
178 | margin-top:0%;
179 | margin-bottom: 2%;
180 | border:0px;
181 | box-sizing:border-box;
182 | }
183 |
184 | .submit-button:hover{
185 | color:$charcoal;
186 | background-color:white;
187 | border:1px solid $charcoal;
188 | cursor:pointer;
189 | transition: .5s;
190 | }
191 |
192 | // unsolved problem modal
193 | .unsolved-problem-container{
194 | padding:2% 0;
195 | }
196 |
197 | .unsolved-problem-container:nth-child(odd){
198 | background-color:rgba($charcoal, .1);
199 | }
200 |
201 | .unsolved-problem-container:hover{
202 | background-color:rgba($teal, .5);
203 | }
204 |
205 | .unsolved-problem-title{
206 | text-transform:capitalize;
207 | }
208 |
209 | .unsolved-problem-link{
210 | text-decoration:none;
211 | color:$charcoal;
212 | }
213 |
214 | // footer
215 | footer{
216 | clear:both;
217 | width:100%;
218 |
219 | p{
220 | color:white;
221 | text-align:left;
222 | padding-top:.5%;
223 | margin:0;
224 | font-family:gill sans;
225 | text-transform:uppercase;
226 | text-align:center;
227 | }
228 | }
229 |
230 | .no-result-text{
231 |
232 | h5{
233 | @include smallregularfont;
234 | color: $charcoal;
235 | text-align: center;
236 | }
237 | }
238 |
239 | //post problem modal
240 | .remodal{
241 | padding:0;
242 |
243 | .goldendecor {
244 | background-color: $charcoal;
245 | text-align: center;
246 | margin: 0;
247 | width: 100%;
248 | height: 60px;
249 | }
250 | }
251 |
252 | .modal-header{
253 | @include mediumfont;
254 | color: white;
255 | font-weight: 500;
256 | text-align: center;
257 | margin-bottom: -1%;;
258 | margin-top: 1%;
259 |
260 | hr{
261 | margin-top: 0;
262 | }
263 | }
264 |
265 | .containerDropdown{
266 | display: flex;
267 | flex-direction: row;
268 | justify-content: space-around;
269 | align-items: center;
270 | flex-wrap: wrap;
271 | }
272 |
273 | .select-brand-model-year{
274 | margin-bottom: 2%;
275 |
276 | p{
277 | @include smallfont;
278 | font-weight: 200;
279 | margin-bottom: 10px;
280 | }
281 | }
282 |
283 | .dropdownlist{
284 | background-color: white;
285 | @include smallfont;
286 | color: $charcoal;
287 | font-weight: 200;
288 | }
289 |
290 | .textarea-border{
291 | border: 1px solid #c0bebf;
292 | }
293 |
294 | .no-result-modal{
295 | padding: 10%;
296 |
297 | h5{
298 | @include smallfont;
299 | color: $charcoal;
300 | font-weight: 200;
301 | margin-bottom: 10%;
302 | }
303 |
304 | .post-problem-button{
305 | text-decoration: none;
306 | background-color: $charcoal;
307 | padding: 5%;
308 | color: white;
309 | padding: 5%;
310 | }
311 | }
312 |
313 | .post-problem-button:hover{
314 | text-decoration: none;
315 | background-color:white;
316 | color: $charcoal;
317 | border:1px solid $charcoal;
318 | transition: .5s;
319 | }
320 |
--------------------------------------------------------------------------------
/diag_app/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.4 on 2017-01-11 14:01
3 | from __future__ import unicode_literals
4 |
5 | from django.conf import settings
6 | from django.db import migrations, models
7 | import django.db.models.deletion
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | initial = True
13 |
14 | dependencies = [
15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='Brand',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('name', models.CharField(max_length=25)),
24 | ],
25 | ),
26 | migrations.CreateModel(
27 | name='Commit',
28 | fields=[
29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
30 | ('posted', models.DateTimeField(auto_now=True)),
31 | ('text', models.TextField(max_length=200)),
32 | ],
33 | ),
34 | migrations.CreateModel(
35 | name='Model',
36 | fields=[
37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
38 | ('name', models.CharField(max_length=40)),
39 | ('year', models.IntegerField(default=0)),
40 | ('brand', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='diag_app.Brand')),
41 | ],
42 | ),
43 | migrations.CreateModel(
44 | name='Problem',
45 | fields=[
46 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
47 | ('title', models.CharField(max_length=65)),
48 | ('description', models.TextField(max_length=500)),
49 | ('posted', models.DateTimeField(auto_now=True)),
50 | ('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='diag_app.Model')),
51 | ],
52 | ),
53 | migrations.CreateModel(
54 | name='Problem_Model',
55 | fields=[
56 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
57 | ('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='diag_app.Model')),
58 | ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='diag_app.Problem')),
59 | ],
60 | ),
61 | migrations.CreateModel(
62 | name='Rating',
63 | fields=[
64 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
65 | ('value', models.IntegerField(default=5)),
66 | ],
67 | ),
68 | migrations.CreateModel(
69 | name='Solution',
70 | fields=[
71 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
72 | ('description', models.TextField(max_length=500)),
73 | ('time_required', models.FloatField(default=0)),
74 | ('parts_cost', models.DecimalField(decimal_places=2, max_digits=6)),
75 | ('posted', models.DateTimeField(auto_now=True)),
76 | ('score', models.IntegerField(default=0)),
77 | ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='diag_app.Problem')),
78 | ],
79 | ),
80 | migrations.CreateModel(
81 | name='System',
82 | fields=[
83 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
84 | ('name', models.CharField(max_length=25)),
85 | ],
86 | ),
87 | migrations.CreateModel(
88 | name='Tech',
89 | fields=[
90 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
91 | ('experience', models.IntegerField(default=0)),
92 | ('job_title', models.CharField(max_length=25)),
93 | ('shop', models.CharField(max_length=25)),
94 | ('tech_rating', models.IntegerField(default=0)),
95 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
96 | ],
97 | ),
98 | migrations.CreateModel(
99 | name='Vote',
100 | fields=[
101 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
102 | ('value', models.IntegerField(default=1)),
103 | ('solution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='diag_app.Solution')),
104 | ('tech', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='diag_app.Tech')),
105 | ],
106 | ),
107 | migrations.AddField(
108 | model_name='solution',
109 | name='tech',
110 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='diag_app.Tech'),
111 | ),
112 | migrations.AddField(
113 | model_name='rating',
114 | name='tech',
115 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='diag_app.Tech'),
116 | ),
117 | migrations.AddField(
118 | model_name='problem',
119 | name='system',
120 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='problems', to='diag_app.System'),
121 | ),
122 | migrations.AddField(
123 | model_name='problem',
124 | name='tech',
125 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='problems', to='diag_app.Tech'),
126 | ),
127 | migrations.AddField(
128 | model_name='commit',
129 | name='solution',
130 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commits', to='diag_app.Solution'),
131 | ),
132 | migrations.AddField(
133 | model_name='commit',
134 | name='tech',
135 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='diag_app.Tech'),
136 | ),
137 | ]
138 |
--------------------------------------------------------------------------------
/diag_app/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render, HttpResponse, HttpResponseRedirect
2 | from rest_framework import permissions
3 | from rest_framework.permissions import IsAuthenticated, AllowAny
4 | from rest_framework import viewsets, permissions, generics, filters
5 | from . import models, forms
6 | from .forms import UserForm, TechForm, LoginForm
7 | from .models import Vote, Problem, Solution, Tech, Rating, System, Brand
8 | from .models import Model, Problem_Model, Notification
9 | from .serializers import VoteSerializer, ProblemGetSerializer, UserSerializer
10 | from .serializers import TechGetSerializer, RatingSerializer, SystemSerializer
11 | from .serializers import BrandSerializer, ModelSerializer, ProblemPostSerializer
12 | from .serializers import SolutionPostSerializer, SolutionGetSerializer
13 | from .serializers import TechPostSerializer, NotificationSerializer
14 | from django.views.generic import ListView
15 | from django.contrib.auth import login, authenticate, logout
16 | from django.contrib.auth.decorators import login_required
17 | from django_filters.rest_framework import DjangoFilterBackend
18 | import django_filters
19 | from django.contrib.auth.models import User
20 | from .permissions import IsStaffOrTargetUser
21 |
22 |
23 | def create_account(request):
24 | return render(request, 'create_account.html')
25 |
26 |
27 | def about_us(request):
28 | return render(request, 'about_us.html')
29 |
30 |
31 | def login_user(request):
32 | form = LoginForm(request.POST or None)
33 | if request.POST:
34 | user = authenticate(username = request.POST['username'],
35 | password = request.POST['password'])
36 | if user is not None and user.is_active and form.is_valid():
37 | login(request, user)
38 | return HttpResponseRedirect('/')
39 | else:
40 | return render(request, 'registration/login.html',{
41 | 'login_message' : 'Enter the username and password correctly',})
42 | return render(request, 'registration/login.html')
43 |
44 |
45 | @login_required(login_url='/login/')
46 | def main_page(request):
47 | return render(request, 'main.html')
48 |
49 |
50 | @login_required(login_url='/login/')
51 | def notifications(request):
52 | return render(request, 'notifications.html')
53 |
54 |
55 | @login_required(login_url='/login/')
56 | def problem_list(request, model, id):
57 | return render(request, 'problem_list.html')
58 |
59 |
60 | @login_required(login_url='/login/')
61 | def model_detail(request, id):
62 | return render(request, 'bike_detail.html')
63 |
64 |
65 | @login_required(login_url='/login/')
66 | def problem_detail(request, id):
67 | return render(request, 'problem_detail.html')
68 |
69 |
70 | @login_required(login_url='/login/')
71 | def profile(request):
72 | return render(request, 'profile.html')
73 |
74 |
75 | # class viewsets and filters
76 | class UserView(viewsets.ModelViewSet):
77 | queryset = User.objects.all()
78 | serializer_class = UserSerializer
79 | model = User
80 |
81 | def get_permissions(self):
82 | return (AllowAny() if self.request.method == 'POST'
83 | else IsStaffOrTargetUser()),
84 |
85 |
86 | class SystemViewSet(viewsets.ModelViewSet):
87 | queryset = System.objects.all()
88 | serializer_class = SystemSerializer
89 | permission_classes = (permissions.IsAuthenticated,)
90 |
91 |
92 | class ModelFilter(django_filters.rest_framework.FilterSet):
93 |
94 | class Meta:
95 | model = Model
96 | fields = ['brand', 'year']
97 |
98 |
99 | class ModelViewSet(viewsets.ModelViewSet):
100 | queryset = Model.objects.all().order_by('name')
101 | serializer_class = ModelSerializer
102 | filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
103 | filter_class = ModelFilter
104 | permission_classes = (permissions.IsAuthenticated,)
105 |
106 |
107 | class BrandViewSet(viewsets.ModelViewSet):
108 | queryset = Brand.objects.all().order_by('name')
109 | serializer_class = BrandSerializer
110 | permission_classes = (permissions.IsAuthenticated,)
111 |
112 |
113 | class ProblemFilter(django_filters.rest_framework.FilterSet):
114 | no_solutions = django_filters.BooleanFilter(name='solutions',
115 | lookup_expr='isnull')
116 |
117 | class Meta:
118 | model = Problem
119 | fields = ['system', 'model', 'tech', 'no_solutions']
120 |
121 |
122 | class ProblemGetViewSet(viewsets.ModelViewSet):
123 | queryset = Problem.objects.all().order_by('-posted')
124 | serializer_class = ProblemGetSerializer
125 | filter_backends = (django_filters.rest_framework.DjangoFilterBackend,filters.SearchFilter,)
126 | filter_class = ProblemFilter
127 | search_fields = ['title']
128 | permission_classes = (permissions.IsAuthenticated,)
129 |
130 |
131 | class ProblemPostViewSet(viewsets.ModelViewSet):
132 | queryset = Problem.objects.all()
133 | serializer_class = ProblemPostSerializer
134 | filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
135 | filter_class = ProblemFilter
136 | permission_classes = (permissions.IsAuthenticated,)
137 |
138 |
139 | class SolutionGetViewSet(viewsets.ModelViewSet):
140 | queryset = Solution.objects.all()
141 | serializer_class = SolutionGetSerializer
142 | permission_classes = (permissions.IsAuthenticated,)
143 |
144 |
145 | class SolutionPostViewSet(viewsets.ModelViewSet):
146 | queryset = Solution.objects.all()
147 | serializer_class = SolutionPostSerializer
148 | permission_classes = (permissions.IsAuthenticated,)
149 |
150 |
151 | class VoteFilter(django_filters.rest_framework.FilterSet):
152 |
153 | class Meta:
154 | model = Vote
155 | fields = ['solution', 'tech']
156 |
157 |
158 | class VoteViewSet(viewsets.ModelViewSet):
159 | queryset = Vote.objects.all()
160 | serializer_class = VoteSerializer
161 | filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
162 | filter_class = VoteFilter
163 | permission_classes = (permissions.IsAuthenticated,)
164 |
165 |
166 | class TechGetViewSet(viewsets.ModelViewSet):
167 | queryset = Tech.objects.all()
168 | serializer_class = TechGetSerializer
169 | permission_classes = (permissions.IsAuthenticated,)
170 |
171 |
172 | class TechPostViewSet(viewsets.ModelViewSet):
173 | queryset = Tech.objects.all()
174 | serializer_class = TechPostSerializer
175 |
176 | def get_permissions(self):
177 | return (AllowAny() if self.request.method == 'POST'
178 | else IsStaffOrTargetUser()),
179 |
180 |
181 | class RatingViewSet(viewsets.ModelViewSet):
182 | queryset = Rating.objects.all()
183 | serializer_class = RatingSerializer
184 | permission_classes = (permissions.IsAuthenticated,)
185 |
186 |
187 | class NotificationViewSet(viewsets.ModelViewSet):
188 | queryset = Notification.objects.all()
189 | serializer_class = NotificationSerializer
190 | permission_classes = (permissions.IsAuthenticated,)
191 |
--------------------------------------------------------------------------------
/diag_app/static/css/remodal-default-theme.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Remodal - v1.1.0
3 | * Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking.
4 | * http://vodkabears.github.io/remodal/
5 | *
6 | * Made by Ilya Makarov
7 | * Under MIT License
8 | */
9 |
10 | /* ==========================================================================
11 | Remodal's default mobile first theme
12 | ========================================================================== */
13 |
14 | /* Default theme styles for the background */
15 |
16 | .remodal-bg.remodal-is-opening,
17 | .remodal-bg.remodal-is-opened {
18 | -webkit-filter: blur(3px);
19 | filter: blur(3px);
20 | }
21 |
22 | /* Default theme styles of the overlay */
23 |
24 | .remodal-overlay {
25 | background: rgba(49, 50, 55, .8);
26 | }
27 |
28 | .remodal-overlay.remodal-is-opening,
29 | .remodal-overlay.remodal-is-closing {
30 | -webkit-animation-duration: 0.3s;
31 | animation-duration: 0.3s;
32 | -webkit-animation-fill-mode: forwards;
33 | animation-fill-mode: forwards;
34 | }
35 |
36 | .remodal-overlay.remodal-is-opening {
37 | -webkit-animation-name: remodal-overlay-opening-keyframes;
38 | animation-name: remodal-overlay-opening-keyframes;
39 | }
40 |
41 | .remodal-overlay.remodal-is-closing {
42 | -webkit-animation-name: remodal-overlay-closing-keyframes;
43 | animation-name: remodal-overlay-closing-keyframes;
44 | }
45 |
46 | /* Default theme styles of the wrapper */
47 |
48 | .remodal-wrapper {
49 | padding: 10px 10px 0;
50 | }
51 |
52 | /* Default theme styles of the modal dialog */
53 |
54 | .remodal {
55 | box-sizing: border-box;
56 | width: 100%;
57 | margin-bottom: 10px;
58 | padding: 0;
59 |
60 | -webkit-transform: translate3d(0, 0, 0);
61 | transform: translate3d(0, 0, 0);
62 |
63 | color: #2b2e38;
64 | background: #fff;
65 | }
66 |
67 | .remodal.remodal-is-opening,
68 | .remodal.remodal-is-closing {
69 | -webkit-animation-duration: 0.3s;
70 | animation-duration: 0.3s;
71 | -webkit-animation-fill-mode: forwards;
72 | animation-fill-mode: forwards;
73 | }
74 |
75 | .remodal.remodal-is-opening {
76 | -webkit-animation-name: remodal-opening-keyframes;
77 | animation-name: remodal-opening-keyframes;
78 | }
79 |
80 | .remodal.remodal-is-closing {
81 | -webkit-animation-name: remodal-closing-keyframes;
82 | animation-name: remodal-closing-keyframes;
83 | }
84 |
85 | /* Vertical align of the modal dialog */
86 |
87 | .remodal,
88 | .remodal-wrapper:after {
89 | vertical-align: middle;
90 | }
91 |
92 | /* Close button */
93 |
94 | .remodal-close {
95 | position: absolute;
96 | top: 0;
97 | left: 0;
98 |
99 | display: block;
100 | overflow: visible;
101 |
102 | width: 35px;
103 | height: 35px;
104 | margin: 0;
105 | padding: 0;
106 |
107 | cursor: pointer;
108 | -webkit-transition: color 0.2s;
109 | transition: color 0.2s;
110 | text-decoration: none;
111 |
112 | color: #95979c;
113 | border: 0;
114 | outline: 0;
115 | background: transparent;
116 | }
117 |
118 | .remodal-close:hover,
119 | .remodal-close:focus {
120 | color: #2b2e38;
121 | }
122 |
123 | .remodal-close:before {
124 | font-family: Arial, "Helvetica CY", "Nimbus Sans L", sans-serif !important;
125 | font-size: 25px;
126 | line-height: 35px;
127 |
128 | position: absolute;
129 | top: 0;
130 | left: 0;
131 |
132 | display: block;
133 |
134 | width: 35px;
135 |
136 | content: "\00d7";
137 | text-align: center;
138 | }
139 |
140 | /* Dialog buttons */
141 |
142 | .remodal-confirm,
143 | .remodal-cancel {
144 | font: inherit;
145 |
146 | display: inline-block;
147 | overflow: visible;
148 |
149 | min-width: 110px;
150 | margin: 0;
151 | padding: 12px 0;
152 |
153 | cursor: pointer;
154 | -webkit-transition: background 0.2s;
155 | transition: background 0.2s;
156 | text-align: center;
157 | vertical-align: middle;
158 | text-decoration: none;
159 |
160 | border: 0;
161 | outline: 0;
162 | }
163 |
164 | .remodal-confirm {
165 | color: #fff;
166 | background: #81c784;
167 | }
168 |
169 | .remodal-confirm:hover,
170 | .remodal-confirm:focus {
171 | background: #66bb6a;
172 | }
173 |
174 | .remodal-cancel {
175 | color: #fff;
176 | background: #e57373;
177 | }
178 |
179 | .remodal-cancel:hover,
180 | .remodal-cancel:focus {
181 | background: #ef5350;
182 | }
183 |
184 | /* Remove inner padding and border in Firefox 4+ for the button tag. */
185 |
186 | .remodal-confirm::-moz-focus-inner,
187 | .remodal-cancel::-moz-focus-inner,
188 | .remodal-close::-moz-focus-inner {
189 | padding: 0;
190 |
191 | border: 0;
192 | }
193 |
194 | /* Keyframes
195 | ========================================================================== */
196 |
197 | @-webkit-keyframes remodal-opening-keyframes {
198 | from {
199 | -webkit-transform: scale(1.05);
200 | transform: scale(1.05);
201 |
202 | opacity: 0;
203 | }
204 | to {
205 | -webkit-transform: none;
206 | transform: none;
207 |
208 | opacity: 1;
209 | }
210 | }
211 |
212 | @keyframes remodal-opening-keyframes {
213 | from {
214 | -webkit-transform: scale(1.05);
215 | transform: scale(1.05);
216 |
217 | opacity: 0;
218 | }
219 | to {
220 | -webkit-transform: none;
221 | transform: none;
222 |
223 | opacity: 1;
224 | }
225 | }
226 |
227 | @-webkit-keyframes remodal-closing-keyframes {
228 | from {
229 | -webkit-transform: scale(1);
230 | transform: scale(1);
231 |
232 | opacity: 1;
233 | }
234 | to {
235 | -webkit-transform: scale(0.95);
236 | transform: scale(0.95);
237 |
238 | opacity: 0;
239 | }
240 | }
241 |
242 | @keyframes remodal-closing-keyframes {
243 | from {
244 | -webkit-transform: scale(1);
245 | transform: scale(1);
246 |
247 | opacity: 1;
248 | }
249 | to {
250 | -webkit-transform: scale(0.95);
251 | transform: scale(0.95);
252 |
253 | opacity: 0;
254 | }
255 | }
256 |
257 | @-webkit-keyframes remodal-overlay-opening-keyframes {
258 | from {
259 | opacity: 0;
260 | }
261 | to {
262 | opacity: 1;
263 | }
264 | }
265 |
266 | @keyframes remodal-overlay-opening-keyframes {
267 | from {
268 | opacity: 0;
269 | }
270 | to {
271 | opacity: 1;
272 | }
273 | }
274 |
275 | @-webkit-keyframes remodal-overlay-closing-keyframes {
276 | from {
277 | opacity: 1;
278 | }
279 | to {
280 | opacity: 0;
281 | }
282 | }
283 |
284 | @keyframes remodal-overlay-closing-keyframes {
285 | from {
286 | opacity: 1;
287 | }
288 | to {
289 | opacity: 0;
290 | }
291 | }
292 |
293 | /* Media queries
294 | ========================================================================== */
295 |
296 | @media only screen and (min-width: 641px) {
297 | .remodal {
298 | max-width: 700px;
299 | }
300 | }
301 |
302 | /* IE8
303 | ========================================================================== */
304 |
305 | .lt-ie9 .remodal-overlay {
306 | background: #cab298;
307 | }
308 |
309 | .lt-ie9 .remodal {
310 | width: 700px;
311 | }
312 |
--------------------------------------------------------------------------------
/diag_app/static/js/problem_detail.js:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | FILE SUMMARY
3 | This file handles the problem detail page as well as posting sloutions, voting
4 | for the best solution and updating the rating for the tech that posted that
5 | solution. There are a couple of repetitive functions here such as the char
6 | counter and there are also helpers here that could be split out into a help file.
7 | *******************************************************************************/
8 | /////////////////////////////////////////////
9 | // Set up Dom
10 | ////////////////////////////////////////////
11 | function setup() {
12 | var url = window.location.href;
13 | showProblem(url);
14 | ///start char counter///
15 | charRemaining()
16 | //click events
17 | $("#newSolutionSubmit").click(postSolution)
18 | }
19 | setup()
20 | //////////////////////////////////////////////////////////////////
21 | // function: showProblem
22 | // parameters: URL
23 | // description: This function loads the problem detail page for
24 | // a given problem.
25 | // return: none
26 | //////////////////////////////////////////////////////////////////
27 | function showProblem(url) {
28 | var id = url.split('/');
29 | id = id[4];
30 | var url = '/api/get-problems/' + id;
31 | $.ajax({
32 | url: url,
33 | type: 'GET',
34 | }).done(function(results){
35 | if (typeof results !== 'undefined') {
36 | var source1 = $("#problem-template").html();
37 | var template1 = Handlebars.compile(source1);
38 | var html1 = template1(results);
39 | $("#problemDetail").append(html1);
40 |
41 | var source2 = $("#solution-template").html();
42 | var template2 = Handlebars.compile(source2);
43 | var html2 = template2(results.solutions);
44 | $("#solutions").append(html2);
45 | }
46 | });
47 | }
48 | //////////////////////////////////////////////////////////////////
49 | // function: charRemaining
50 | // parameters: none
51 | // description: Displays Chars remaining for text fields
52 | // return: none
53 | //////////////////////////////////////////////////////////////////
54 |
55 | // TEST THIS
56 |
57 | function charRemaining() {
58 | $('#solutionText').keyup(function () {
59 | var left = 500 - $(this).val().length;
60 | if (left < 0) {
61 | left = 0;
62 | }
63 | $('#counterSolution').text('Characters left: ' + left);
64 | });
65 | $('#probText').keyup(function () {
66 | var left = 500 - $(this).val().length;
67 | if (left < 0) {
68 | left = 0;
69 | }
70 | $('#counterProb').text('Characters left: ' + left);
71 | });
72 |
73 | }
74 | //////////////////////////////////////////////////////////////////
75 | // function: postSolution
76 | // parameters: none
77 | // description: Posts a new solution to the DB
78 | // return: none
79 | //////////////////////////////////////////////////////////////////
80 | function postSolution() {
81 | var text = $("#solutionText").val();
82 | var user = $("#userId").val();
83 | var issue = $("#problemId").val();
84 | var time = $("#solutionTime").val();
85 | var cost = $("#partsCost").val();
86 | var context = {
87 | time_required: time,
88 | description: text,
89 | tech: user,
90 | parts_cost: cost,
91 | problem: issue,
92 | }
93 | console.log(context);
94 | $.ajax({
95 | url: '/api/post-solutions/',
96 | type: 'POST',
97 | data: context,
98 | }).done(function(results){
99 | // fix this: reload just solution container.
100 | // location.reload();
101 | $("#solutions").load(location.href + " #solutions");
102 | })
103 |
104 | }
105 | //////////////////////////////////////////////////////////////////
106 | // function: postSolution
107 | // parameters: none
108 | // description: Validates vote so user can not vote multiple times.
109 | // Should keep user from voting on their own solutions.
110 | // return: none
111 | //////////////////////////////////////////////////////////////////
112 | // better way to validate votes?
113 | function validateVote(solutionId, value) {
114 | var user = $("#userId").val();
115 | var voted = [];
116 | var vote = {};
117 | $.ajax({
118 | url: '/api/votes?solution=' + solutionId,
119 | type: 'GET',
120 | }).done(function(results) {
121 | var votes = results.results;
122 | for (var i=0; i < votes.length; i++) {
123 | if (user == votes[i].tech) {
124 | vote['tech'] = user;
125 | voted.push(vote);
126 | }
127 | }
128 | if (voted.length > 0){
129 | alert("You've already voted for that one!");
130 | } else {
131 | postVote(solutionId, value);
132 | }
133 | });
134 | }
135 | //////////////////////////////////////////////////////////////////////
136 | // function: postVote
137 | // parameters: Vote ID and Vote value(default is 1)
138 | // description: Posts new vote to DB. Calls updateScore for solution.
139 | // return: none
140 | //////////////////////////////////////////////////////////////////////
141 | function postVote(id, value) {
142 | var user = $("#userId").val();
143 | var voteValue = value;
144 | var votedFor = id;
145 | var context = {
146 | tech: user,
147 | value: voteValue,
148 | solution: votedFor,
149 | }
150 | $.ajax({
151 | url: '/api/votes/',
152 | type: 'POST',
153 | data: context,
154 | }).done(function(results) {
155 | updateScore(id, value);
156 | });
157 | }
158 | //////////////////////////////////////////////////////////////////////
159 | // function: updateScore
160 | // parameters: solution id and vote's value
161 | // description: GETS and updates the score for a given solution.
162 | // Calls updateRating to update the poster's rating.
163 | // return: none
164 | //////////////////////////////////////////////////////////////////////
165 | function updateScore(id, voteValue) {
166 | $.ajax({
167 | url: '/api/get-solutions/' + id ,
168 | type: 'GET',
169 | }).done(function(results) {
170 | var tech = results.tech.id;
171 | updateRating(tech, voteValue);
172 | var currentScore = results.score;
173 | var newScore = currentScore + voteValue;
174 | var context = {
175 | score: newScore
176 | }
177 | $.ajax({
178 | url: '/api/post-solutions/' + id + '/',
179 | type: 'PATCH',
180 | data: context,
181 | }).done(function(results) {
182 | var id_container = '#solutionScore' + results.id;
183 | $(id_container).html('Score: ' + results.score);
184 | });
185 | });
186 | }
187 | //////////////////////////////////////////////////////////////////////
188 | // function: updateRating
189 | // parameters: tech's id and vote's value
190 | // description: GETS and updates the rating for a given tech.
191 | // return: none
192 | //////////////////////////////////////////////////////////////////////
193 | function updateRating(tech, voteValue) {
194 | $.ajax({
195 | url: '/api/get-techs/' + tech,
196 | type: 'GET',
197 | }).done(function(results) {
198 | var currentRating = results.tech_rating
199 | var newRating = currentRating + (voteValue * 5)
200 | context = {
201 | tech_rating: newRating
202 | }
203 | $.ajax({
204 | url: '/api/post-techs/' + tech + '/',
205 | type: 'PATCH',
206 | data: context,
207 | }).done(function(results) {
208 | })
209 |
210 | })
211 |
212 | }
213 | ////////////////////////////////////////////////////////////////////////////////
214 | // Helper: formatTime
215 | // parameters: timestamp
216 | // description: Formats timestamps to make them pretty and more human readable.
217 | // return: String of month/day/year
218 | ////////////////////////////////////////////////////////////////////////////////
219 | Handlebars.registerHelper('formatTime', function (posted) {
220 | var time = posted.replace('T', ':');
221 | var date = time.split(":")[0];
222 | var year = Number(date.split("-")[0]);
223 | var month = Number(date.split("-")[1]);
224 | var day = Number(date.split("-")[2]);
225 | var months = {
226 | "January": 1,
227 | "February ": 2,
228 | "March": 3,
229 | "April": 4,
230 | "May": 5,
231 | "June": 6,
232 | "July": 7,
233 | "August": 8,
234 | "September": 9,
235 | "October": 10,
236 | "November": 11,
237 | "December": 12,
238 | };
239 | for(var i in months){
240 | if(month == months[i]){
241 | month = i;
242 | }
243 | }
244 | return month + " " + day + " " + year;
245 | })
246 |
--------------------------------------------------------------------------------
/diag_app/static/css/_index.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 | @import "mixins";
3 |
4 |
5 | .indexbody{
6 | margin: 0;
7 | background-image:
8 | url("../images/index-background.jpg");
9 | background-repeat: no-repeat;
10 | -webkit-background-size: cover;
11 | -moz-background-size: cover;
12 | -o-background-size: cover;
13 | background-size: cover;
14 | background-repeat:no-repeat;
15 | }
16 |
17 | .indexnav{
18 | width:11%;
19 | margin-left:14%;
20 | background-color:rgba(white, .3);
21 | padding:1%;
22 | margin-bottom:0;
23 | height: 100%;
24 |
25 | ul{
26 | text-align:right;
27 | text-transform:uppercase;
28 | padding:0;
29 | margin:0;
30 | margin-bottom:20%;
31 |
32 | li{
33 | clear:both;
34 | list-style-type:none;
35 | padding-bottom:1%;
36 | @include regularfont;
37 |
38 | a{
39 | text-decoration:none;
40 | color:white;
41 | position:relative;
42 |
43 | }
44 |
45 | a:after{
46 | display:block;
47 | position:absolute;
48 | bottom:0px;
49 | height:1.2em;
50 | width:0px;
51 | background-color:$teal;
52 | transition:width .5s;
53 | content:"";
54 | z-index:-1;
55 | }
56 |
57 | .link1:hover:after{
58 | width:150%;
59 | }
60 |
61 | .link2:hover:after{
62 | width:134%;
63 | }
64 |
65 | .link3:hover:after{
66 | width:133%;
67 | }
68 | }
69 |
70 | .logobox{
71 | width:100%;
72 | margin-bottom:56%;
73 |
74 | .logo{
75 | width:100%;
76 | }
77 | }
78 | }
79 | }
80 |
81 |
82 |
83 | .loginform .loginfield{
84 | height:30px;
85 | border:transparent;
86 | width: 100%;
87 | margin:5% auto;
88 | padding:5%;
89 | box-sizing:border-box;
90 | }
91 |
92 | input:-webkit-autofill {
93 | -webkit-box-shadow: 0 0 0px 1000px white inset;
94 | }
95 |
96 | .loginbutton{
97 | height:50px;
98 | width:100%;
99 | text-align: center;
100 | padding:0;
101 | background-color:$charcoal;
102 | @include regularfont;
103 | color: white;
104 | text-align:center;
105 | margin:5% auto;
106 | -webkit-transition-duration: 0.4s; /* Safari */
107 | transition-duration: 0.4s;
108 | border: 0;
109 | cursor: pointer;
110 |
111 |
112 |
113 | a{
114 | text-decoration:none;
115 | color:white;
116 | padding:15%;
117 | }
118 |
119 | a:hover{
120 | text-decoration:none;
121 | color:$teal;
122 | }
123 | }
124 |
125 | .loginbutton:hover{
126 | background-color:white;
127 | color:$charcoal;
128 | }
129 |
130 | /* ----------- iPad mini ----------- */
131 |
132 | /* Portrait and Landscape */
133 | @media only screen
134 | and (min-device-width: 768px)
135 | and (max-device-width: 1024px)
136 | and (-webkit-min-device-pixel-ratio: 1)
137 | {
138 |
139 | .indexbody{
140 | background:
141 | url("../images/media-index-background.jpg");
142 | background-width: auto;
143 | background-height: 100%;
144 | background-position: center;
145 | background-repeat: no-repeat;
146 | background-position: 40% 10%;
147 | margin:0;
148 | }
149 |
150 | .indexnav{
151 | height:100%;
152 | width:60%;
153 | margin:10%;
154 | background-color:rgba(#afafb2, .35);
155 | padding:10%;
156 | float:left;
157 | overflow:hidden;
158 |
159 | ul{
160 | text-align:left;
161 | text-transform:uppercase;
162 | padding:0;
163 | margin:0;
164 | margin-bottom:5%;
165 | float:left;
166 |
167 | li{
168 | clear:both;
169 | list-style-type:none;
170 | padding-bottom:1%;
171 | @include regularfont;
172 |
173 | a{
174 | text-decoration:none;
175 | color:white;
176 | position:relative;
177 | }
178 |
179 | a:after{
180 | display:block;
181 | position:absolute;
182 | bottom:0px;
183 | height:1.2em;
184 | width:0px;
185 | background-color:$golden;
186 | transition:width .5s;
187 | content:"";
188 | z-index:-1;
189 | }
190 |
191 | .link1:hover:after{
192 | width:155%;
193 | }
194 |
195 | .link2:hover:after{
196 | width:165%;
197 | }
198 |
199 | .link3:hover:after{
200 | width:138%;
201 | }
202 | }
203 |
204 | .logobox{
205 | width:100%;
206 | margin-bottom:35%;
207 |
208 | .logo{
209 | height:160px;
210 | width: auto;
211 | }
212 | }
213 | }
214 | }
215 | }
216 |
217 | /* ----------- iPad 1 and 2 ----------- */
218 | /* Portrait and Landscape */
219 | @media only screen
220 | and (min-device-width: 768px)
221 | and (max-device-width: 1024px)
222 | and (-webkit-min-device-pixel-ratio: 1)
223 | {
224 |
225 | .indexbody{
226 | background:
227 | url("../images/media-index-background.jpg");
228 | background-width: auto;
229 | background-height: 100%;
230 | background-position: center;
231 | background-repeat: no-repeat;
232 | background-position: 40% 10%;
233 | margin:0;
234 | }
235 |
236 | .indexnav{
237 | height:100%;
238 | width:60%;
239 | margin:10%;
240 | background-color:rgba(#afafb2, .35);
241 | padding:10%;
242 | float:left;
243 | overflow:hidden;
244 |
245 |
246 | ul{
247 | text-align:left;
248 | text-transform:uppercase;
249 | padding:0;
250 | margin:0;
251 | margin-bottom:15%;
252 | float:left;
253 |
254 | li{
255 | clear:both;
256 | list-style-type:none;
257 | padding-bottom:5%;
258 | @include regularfont;
259 |
260 | a{
261 | text-decoration:none;
262 | color:white;
263 | position:relative;
264 | }
265 |
266 | a:after{
267 | display:block;
268 | position:absolute;
269 | bottom:0px;
270 | height:1.2em;
271 | width:0px;
272 | background-color:$golden;
273 | transition:width .5s;
274 | content:"";
275 | z-index:-1;
276 | }
277 |
278 | .link1:hover:after{
279 | width:155%;
280 | }
281 |
282 | .link2:hover:after{
283 | width:165%;
284 | }
285 |
286 | .link3:hover:after{
287 | width:138%;
288 | }
289 | }
290 |
291 | .logobox{
292 | width:100%;
293 | margin-bottom:35%;
294 |
295 | .logo{
296 | height:275px;
297 | width: auto;
298 | }
299 | }
300 | }
301 | }
302 | }
303 |
304 |
305 | /* Portrait and Landscape */
306 | @media only screen
307 | and (min-device-width: 320px)
308 | and (max-device-width: 667px)
309 | and (-webkit-min-device-pixel-ratio: 2) {
310 |
311 | .indexbody{
312 | background:
313 | url("../images/media-index-background.jpg");
314 | background-width: auto;
315 | background-height: 100%;
316 | background-position: center;
317 | background-repeat: no-repeat;
318 | background-position: 40% 10%;
319 | margin:0;
320 | }
321 |
322 | .indexnav{
323 | height:100%;
324 | width:60%;
325 | margin:10%;
326 | background-color:rgba(#afafb2, .35);
327 | padding:10%;
328 | float:left;
329 | overflow:hidden;
330 |
331 | ul{
332 | text-align:left;
333 | text-transform:uppercase;
334 | padding:0;
335 | margin:0;
336 | margin-bottom:5%;
337 | float:left;
338 |
339 | li{
340 | clear:both;
341 | list-style-type:none;
342 | padding-bottom:1%;
343 | @include regularfont;
344 |
345 | a{
346 | text-decoration:none;
347 | color:white;
348 | position:relative;
349 | }
350 |
351 | a:after{
352 | display:block;
353 | position:absolute;
354 | bottom:0px;
355 | height:1.2em;
356 | width:0px;
357 | background-color:$golden;
358 | transition:width .5s;
359 | content:"";
360 | z-index:-1;
361 | }
362 |
363 | .link1:hover:after{
364 | width:155%;
365 | }
366 |
367 | .link2:hover:after{
368 | width:165%;
369 | }
370 |
371 | .link3:hover:after{
372 | width:138%;
373 | }
374 | }
375 |
376 | .logobox{
377 | width:100%;
378 | margin-bottom:35%;
379 |
380 | .logo{
381 | height:150px;
382 | width: auto;
383 | }
384 | }
385 | }
386 | }
387 | }
388 |
--------------------------------------------------------------------------------
/diag_app/static/js/script.js:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////
2 | // Set up Dom
3 | //////////////////////////////////////////////////////////////////
4 | function setupEvents() {
5 | $("#notify").click(loadNotificationsModal);
6 | $("#newProbSubmit").click(postProblem);
7 | $("#answer").click(loadUnsolvedProblemsModal);
8 | $("#searchButton").click(searchProblems);
9 | loadBrandsAskModal();
10 | charRemainingText();
11 | //Dropdown menu on nav bar
12 | $(document).ready(function(){
13 | $(".link4").click(function(){
14 | $(".dropdown").slideToggle();
15 | });
16 | });
17 | }
18 | setupEvents();
19 | //////////////////////////////////////////////////////////////////
20 | // function: loadBrandsAskModal
21 | // parameters: none
22 | // description: This function loads the brands currently in the DB
23 | // return: none
24 | //////////////////////////////////////////////////////////////////
25 | function loadBrandsAskModal() {
26 | loadSystemsAskModal()
27 | $.ajax({
28 | url: '/api/brands/',
29 | type: 'GET',
30 | }).done(function(results) {
31 | $('#brandSelect').empty();
32 | var source = $('#brand-modal-template').html();
33 | var template = Handlebars.compile(source);
34 | var html = template(results.results);
35 | $('#brandSelect').append(html);
36 | var brand = $("#probBrand option:selected").val();
37 | loadYearsAskModal(brand);
38 | })
39 | }
40 | //////////////////////////////////////////////////////////////////////////
41 | // function: loadYearsAskModal
42 | // parameters: brand id
43 | // description: This function loads the years for the brand selected.
44 | // return: none
45 | //////////////////////////////////////////////////////////////////////////
46 | function loadYearsAskModal(id) {
47 | $.ajax({
48 | url: '/api/models?brand=' + id,
49 | type: 'GET'
50 | }).done(function(results) {
51 | $('#yearSelect').empty()
52 | var bike = results.results
53 | var years = []
54 | for (var i=0; i < bike.length; i++) {
55 | if (!(years.includes(bike[i].year))) {
56 | years.push(bike[i].year);
57 | }
58 | }
59 | years.sort(function(a, b) { return b-a })
60 | var source = $('#year-modal-template').html();
61 | var template = Handlebars.compile(source);
62 | var html = template(years);
63 | $('#yearSelect').append(html);
64 | loadModelsAskModal();
65 | });
66 | }
67 | //////////////////////////////////////////////////////////////////////////
68 | // function: loadModelsAskModal
69 | // parameters: year
70 | // description: This function loads the models for the brand/year selected.
71 | // return: none
72 | //////////////////////////////////////////////////////////////////////////3
73 | function loadModelsAskModal(year) {
74 | var brandId = $("#probBrand option:selected").val();
75 | $.ajax({
76 | url: '/api/models?brand=' + brandId + '&year=' + year,
77 | type: 'GET'
78 | }).done(function(results) {
79 | $('#modelSelect').empty();
80 | var bikes = results.results;
81 | var source = $('#model-modal-template').html();
82 | var template = Handlebars.compile(source);
83 | var html = template(bikes);
84 | $('#modelSelect').append(html);
85 | });
86 | }
87 | //////////////////////////////////////////////////////////////////////////
88 | // function: loadSystemsAskModal
89 | // parameters: none
90 | // description: This function loads the systems for the select menu
91 | // return: none
92 | //////////////////////////////////////////////////////////////////////////
93 | function loadSystemsAskModal() {
94 | $.ajax({
95 | url: '/api/systems/',
96 | type: 'GET',
97 | }).done(function(results) {
98 | $('#systemSelect').empty();
99 | var systems = results.results;
100 | var source = $('#system-modal-template').html();
101 | var template = Handlebars.compile(source);
102 | var html = template(systems);
103 | $('#systemSelect').append(html);
104 | })
105 | }
106 |
107 | //////////////////////////////////////////////////////////////////////////
108 | // function: charRemainingText
109 | // parameters: none
110 | // description: This function displys chars remainging for problem text.
111 | // return: none
112 | //////////////////////////////////////////////////////////////////////////
113 | function charRemainingText() {
114 | $('#probText').keyup(function () {
115 | var left = 500 - $(this).val().length;
116 | if (left < 0) {
117 | left = 0;
118 | }
119 | $('#counter').text('Characters left: ' + left);
120 | })
121 | }
122 | //////////////////////////////////////////////////////////////////////////
123 | // function: postProblem
124 | // parameters: none
125 | // description: This function posts a problem to the DB via Ajax.
126 | // return: none
127 | //////////////////////////////////////////////////////////////////////////
128 | function postProblem() {
129 | var modal = $('[data-remodal-id=askModal]').remodal();
130 | var bike = $("#probModel option:selected");
131 | var sys = $("#probSystem option:selected");
132 | var text = $("#probText");
133 | var user = $("#userId");
134 | var header = $("#probTitle");
135 | var context = {
136 | system: sys.val(),
137 | description: text.val(),
138 | tech: user.val(),
139 | model: bike.val(),
140 | title: header.val(),
141 | }
142 | $.ajax({
143 | url: '/api/post-problems/',
144 | type: 'POST',
145 | data: context,
146 | success: function (response) {
147 | modal.close();
148 | alert("Problem saved successfully!");
149 | },
150 | error: function (xhr, ajaxOptions, thrownError) {
151 | $("p.error-message").empty();
152 | if (thrownError === 'Bad Request') {
153 | $("p.error-message").append("All the fields are required to create a problem, please check if you've filled them properly and try again.");
154 | } else if (thrownError === 'Internal Server Error') {
155 | $("p.error-message").append("There were an internal error saving your problem. Please try again later or contact us describing your issue.");
156 | } else if (thrownError === 'Forbidden') {
157 | $("p.error-message").append("You don't have permissions to create a problem.");
158 | } else {
159 | $("p.error-message").append("Some weird problem occurred, the description is: Error code = " + xhr.status + " Error Message = " + thrownError + " please try again later or contact us!");
160 | }
161 | }
162 | }).done(function(){
163 | $('.newProblem').trigger('reset');
164 | });
165 | }
166 |
167 | // This function controls if the all the form fields have been filled and unblocks the submit button
168 | $(".newProblem").on('change mouseover', function(){
169 | var bike = $("#probModel option:selected").val();
170 | var sys = $("#probSystem option:selected").val();
171 | var text = $("#probText").val();
172 | var user = $("#userId").val();
173 | var header = $("#probTitle").val();
174 | if ((bike === 'none') || (sys === 'none') || !(text) || !(user) || !(header)){
175 | $("input#newProbSubmit.submit-button").prop("disabled", true);
176 | } else {
177 | $("input#newProbSubmit.submit-button").prop("disabled", false);
178 | }
179 | });
180 | //////////////////////////////////////////////////////////////////////////
181 | // function: loadUnsolvedProblemsModal
182 | // parameters: none
183 | // description: This functions pulls unsolved problems from the DB via Ajax.
184 | // return: none
185 | //////////////////////////////////////////////////////////////////////////
186 | function loadUnsolvedProblemsModal() {
187 | console.log("loadUnsolvedProblemsModal");
188 | $.ajax({
189 | url: '/api/get-problems?no_solutions=True',
190 | type: 'GET',
191 | }).done(function(results) {
192 | var problems = results.results;
193 | var source = $('#unsolved-problem-template').html();
194 | var template = Handlebars.compile(source);
195 | var html = template(problems);
196 | $('#modalProblemList').empty();
197 | $('#modalProblemList').append(html);
198 | });
199 | }
200 | ////////////////////////////////////////////////////////////////////////////////
201 | // Helper: formatTime
202 | // parameters: timestamp
203 | // description: Formats timestamps to make them pretty and more human readable.
204 | // return: String of month/day/year
205 | ////////////////////////////////////////////////////////////////////////////////
206 | Handlebars.registerHelper('formatTime', function (posted) {
207 | var time = posted.replace('T', ':');
208 | var date = time.split(":")[0];
209 | var year = Number(date.split("-")[0]);
210 | var month = Number(date.split("-")[1]);
211 | var day = Number(date.split("-")[2]);
212 | var months = {
213 | "January": 1,
214 | "February ": 2,
215 | "March": 3,
216 | "April": 4,
217 | "May": 5,
218 | "June": 6,
219 | "July": 7,
220 | "August": 8,
221 | "September": 9,
222 | "October": 10,
223 | "November": 11,
224 | "December": 12,
225 | }
226 | for (var i in months) {
227 | if (month === months[i]) {
228 | month = i;
229 | }
230 | }
231 | return month + " " + day + " " + year;
232 | })
233 | ////////////////////////////////////////////////////////////////////////////////
234 | // Helper: linkURL
235 | // parameters: problem object
236 | // description: Formats URL for a given problem.
237 | // return: String of URL for problem detail page.
238 | ////////////////////////////////////////////////////////////////////////////////
239 | Handlebars.registerHelper('linkURL', function (object) {
240 | id = Handlebars.Utils.escapeExpression(object.id);
241 | title = Handlebars.Utils.escapeExpression(object.title);
242 | url = '/problem_detail/' + id;
243 | return '
' + title + ' ';
244 | });
245 | ////////////////////////////////////////////////////////////////////////////////
246 | // Helper: searchProblems
247 | // parameters: none
248 | // description: Search function for nav bar.
249 | // return: none
250 | ////////////////////////////////////////////////////////////////////////////////
251 | function searchProblems() {
252 | var searchTerm = $("#searchBox").val();
253 | $.ajax({
254 | url: '/api/get-problems?search=' + searchTerm,
255 | type: 'GET'
256 | }).done(function(results) {
257 | var problems = results.results;
258 | var length = problems.length;
259 | var message = '
' + "There are no problems that match your search."+ ' ' +
260 | '
' + "Post a new problem here" + ' ';
261 | var noResults = {
262 | message: message,
263 | }
264 | if (length == 0) {
265 | var source = $('#search-problem-template-two').html();
266 | var template = Handlebars.compile(source);
267 | var html = template(noResults);
268 | $('#searchProblemList').empty();
269 | $('#searchProblemList').append(html);
270 | } else {
271 | var source = $('#search-problem-template').html();
272 | var template = Handlebars.compile(source);
273 | var html = template(problems);
274 | $('#searchProblemList').empty();
275 | $('#searchProblemList').append(html);
276 | }
277 | });
278 | }
279 | ////////////////////////////////////////////////////////////////////////////////
280 | // Helper: loadNotificationsModal
281 | // parameters: tech id
282 | // description: Load notifications for a given user via Ajax.
283 | // return: String of month/day/year
284 | ////////////////////////////////////////////////////////////////////////////////
285 | /*IN PROGRESS*/
286 | function loadNotificationsModal() {
287 | $.ajax({
288 | url: '/api/notifications/',
289 | type: 'GET',
290 | }).done(function(results) {
291 | var notifications = results.results;
292 | var source = $('#notification-template').html();
293 | var template = Handlebars.compile(source);
294 | var html = template(notifications);
295 | $('#notifyList').empty();
296 | $('#notifyList').append(html);
297 | });
298 | }
299 |
--------------------------------------------------------------------------------
/diag_app/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 | {%load static%}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {% block stylecontent %}{% endblock %}
13 |
14 | {% block titlecontent %}
Base {% endblock %}
15 |
16 | {% block bodycontent %}{% endblock %}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {%csrf_token%}
26 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Diagnose
52 |
53 |
54 |
55 |
56 |
Post a Problem
57 |
58 |
59 |
60 |
61 | {%csrf_token%}
62 |
63 |
64 |
65 |
66 |
67 | Choose Bike:
68 |
69 |
72 |
75 |
78 |
79 |
Choose System:*
80 |
81 |
82 |
83 |
84 |
85 | Enter a title for your post:*
86 |
87 | Describe your problem:*
88 |
89 |
90 | All fields with * are required to submit the form.
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
Solve a problem
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | My Account
113 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | {% block content %}
143 | {% endblock %}
144 |
145 | {% block footercontent %}
146 |
147 | {% endblock %}
148 |
149 |
150 |
151 |
152 |
153 |
154 |
168 |
169 |
170 |
179 |
180 |
181 |
182 |
196 |
197 |
198 |
199 |
211 |
212 |
213 |
225 |
226 |
227 |
239 |
240 |
241 |
253 |
254 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | {% block jscontent %}{% endblock %}
278 |
279 |
280 |
--------------------------------------------------------------------------------