diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..83f2394 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: python +python: +- "3.6" +install: "pip install -r requirements.txt" +script: +- SECRET_KEY="whatever" ./manage.py makemigrations; SECRET_KEY="whatever" ./manage.py migrate; SECRET_KEY="whatever" ./manage.py test diff --git a/README.md b/README.md index 2a6e285..f851dbc 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,56 @@ [![Build Status](https://travis-ci.org/tdunn891/milestone-4.svg?branch=master)](https://travis-ci.org/tdunn891/milestone-4) -## Project Purpose +# Milestone Profile 4 - [TrackIt](https://django-issue-tracker-1.herokuapp.com/) + +# Contents + +1. [**Project Purpose**](#project-purpose) + +2. [**UX**](#ux) + + - [**5 Planes of UX**](#5-planes-of-ux) + - [**User Stories**](#user-stories) + +3. [**Features**](#features) + + - [**Existing Features**](#existing-features) + - [**Potential Future Features**](#potential-future-features) + +4. [**Database**](#Database) + +5. [**Technologies**](#technologies) + +6. [**Testing**](#testing) + + - [**Unit Testing**](#unit-testing) + - [**Manual Testing**](#manual-testing) + - [**Code Validation**](#code-validation) + +7. [**Deployment**](#deployment) + + - [**Heroku**](#heroku) + - [**Local Deployment**](#local-deployment) + - [**Development vs Production Versions**](#development-vs-production-versions) + +8. [**Credits**](#credits) + + - [**Content**](#content) + - [**Media**](#media) + - [**Acknowledgements**](#acknowledgements) + +# Project Purpose TrackIt was developed to allow simple, effective tracking of issues, split into Bugs and Feature Requests. The site was developed to be suitable for all organisation types and would be deployed -## UX +# UX + +## 5 Planes of UX ### Strategy Site Objective: Provide platform to track tickets (Bugs and Feature Reqests). -User Needs: Intuitive way to raise and track their issues through to completion - -Opportunities/Problems table used to determine the strategic priorities UX efforts should address (in this order): - -| Opportunity/Problem | Importance | Viability/Feasibility | -| ----------------------------------------------- | :--------: | :-------------------: | -| A. Track bugs and feature requests | 5 | 5 | -| B. Contribute to to the closing of open tickets | 5 | 5 | -| C. | 2 | 4 | -| D. | 1 | 2 | +User Needs: Intuitive way to raise and track their tickets through to completion. ### Scope @@ -27,21 +58,8 @@ Opportunities/Problems table used to determine the strategic priorities UX effor In considering functional specifications, existing ticket and bug trackers were researched, including GitHub Issues, Jira. This helped to identify the key data fields and features users expect to see. -Feature Set: - -- Tickets View: Filterable, sortable table of tickets. -- Raise Ticket: Raise a bug or feature. -- Edit Ticket: Edit own ticket if submitter, or any ticket if staff. -- Cancel Ticket: Cancel ticket if submitter, or any ticket if staff. -- Kanban (PRO) View: This popular agile tool provides a more visual representation tickets. -- Dashboard View: See various charts at a glance. -- Account View: Update personal details, including Profile Image. -- Checkout: Make a once-off credit card payment (Stripe) to unlock KANBAN view and unlimited ticket submissions. - #### Content Requirements -In order to provide the value of the above features, the following content is required: - - Input boxes for ticket filtering - DC.js, D3.js and Crossfilter for charts on dashboard page. - Dropdowns for adding and editing ticket fields. @@ -69,8 +87,6 @@ All interactive elements provide feedback to the user to encourage interaction a #### Information Architecture -The filtering and sorting panel is located on the left, a logical and intuitive position expected by users. - Pagination Sections are on separate pages for to aide... @@ -79,9 +95,9 @@ Sections are on separate pages for to aide... #### Wireframes -Two sets of wireframes were created in the early development stage to inform the structure and layout for different device sizes. +Two sets of wireframes were created in the early development stage to set out the structure and layout on different device sizes. -[Desktop & Mobile Wireframes](https://github.com/tdunn891/milestone-4) +[Desktop & Mobile Wireframes](https://github.com/tdunn891/milestone-4/tree/master/wireframes) ### Surface @@ -89,50 +105,152 @@ Colours: A minimal, subtle colour scheme was employed so as not to distract from Fonts: Easily readable font was a key consideration. -### User Stories +## User Stories + +As a Guest User... + +- I want to quickly learn the key features of the site. +- I want to know if sign up is free and which features are behind a paywall. +- I want to see a visual preview of the features before having to sign up. +- I want to know if this site is credible and used by well-known organisations. +- I want to see some customer testimonials. +- I want my initial questions to be answered. +- I want to be able to contact the developer for further information. + +As a Registered User... + +- I want to submit a Feature Request that would improve my productivity. +- I want to submit a Bug I am experiencing without being distracted from my core tasks. +- I want to be able to check how many free tickets submissions I have remaining in the month on my Basic plan. +- I want to see a list of all tickets I have raised. +- I want the option to see all tickets in a KANBAN-style column view. +- I want to contribute to the resolution of an open issue by leaving a comment under it. +- I want to raise the profile of an existing ticket by upvoting it. +- I want the ability to unlock PRO features via online credit card payment. +- I want to control which personal information I share with other users. +- I want to be able to upload a profile image so my colleagues can put a face to the name. +- I want to access contact details of other users. +- I want to be able to request Admin access. + +As a Registered Admin User: + +- I want to easily contact a ticket's Assignee by launching a draft email from the ticket view. +- I want to graphically identify which tickets require attention, ie. aged and high priority tickets. +- I want to see which tickets have the most upvotes, to help inform work prioritisation. + +#### User Group Permissions + +| Permission | Submitter | Admin | +| :----------------------------------- | :-------- | :---- | +| Can raise a ticket | Yes | Yes | +| Can edit own ticket | Yes | Yes | +| Can edit any ticket | No | Yes | +| Can reassign own ticket | Yes | Yes | +| Can reassign any ticket | No | Yes | +| Can edit ticket status of own ticket | Yes | Yes | +| Can edit ticket status of any ticket | No | Yes | +| Can be Assignee | Yes | Yes | +| Can delete own ticket | No | Yes | +| Can delete any ticket | No | Yes | + +# Features + +## Pages + +### Home + +The Home page provides a quick introduction to the site, featuring minimal, easy to digest sections on Features, User Testimonials, Trusted By, Plans and Frequently Asked Questions. + +If the user is not signed in, the call to action is to **Create Free Account**, with 3 large blue buttons located in the navigation bar, at the top of the page and in the Plans section. The Log In button for returning users uses an outline style, so as to not detract from the Call to Action. + +If the user is already signed in, their username and profile picture (if any) will be displayed in the navigation bar and acts as a link to their Account page. + +If the user is signed in but not a Pro User, the call to action is to **Go PRO**, with 3 blue buttons located in the navigation bar, top of page and in Plans section. The Go PRO buttons link to the Checkout page, where the user can pay to upgrade their account. + +### Tickets -User stories: +The Tickets page allows sorting and filtering of existing tickets, and a link to create a new ticket. -- User 1 - "As a user who is only interested in tickets I have submitted myself, I want quick access to just my tickets." -- User 2 - "As a team leader who needs to see the status of aging high priority tickets, I want to graphically see which tickets need attention to close." -- User 3 - "As a user who prefers KANBAN-style columns for intuitive task tracking I would like the option to view my tickets in KANBAN view." -- User 4 - "As a user who can contribute to the resolution of an issue, I want to be able to leave a comment under a ticket." -- User 5 - "As a user who wants wants to draw attention to an existing ticket, I want to be able to upvote." -- User 6 - "As a team leader who wants to follow up with the assignee of a ticket, I want to be able to start an email from the ticket view." +The table shows key information for each ticket. Clicking on the row opens the **View Ticket** page for further details. Each field can be sorted both directions on click, and both global and field-specific search is available via input boxes. A Reset filters button clears all filters. Pagination is implemented so the user can choose the number of tickets to be displayed per page. -## Features +Type and Status fields use icons to eliminate cognitive overload. The Priority value (Low, Medium, High) is colour-coded intuitively. The row colour denotes ticket Status (green: Resolved, yellow: In Progress, blue: New, grey: Cancelled). -### Existing Features +The **Raise Ticket** button links to the **Add Ticket** page. + +### View Ticket + +The View Ticket page provides additional ticket information, including the Description, Tags, Days to Resolve (if Resolved), Age (if not Resolved) and Recent Activity. + +If the user is the Submitter or Staff, the **Edit Ticket** and **Status** buttons will be displayed and the user will have permission to change the Status via the dropdown, and to use access the Edit Ticket page. + +As with the Tickets view, ticket colour denotes ticket status. + +The **Upvote** button displays how many upvotes the ticket has received, and increments on click. + +Profile pictures for Submitter and Assignee (if any) are displayed next to their usernames. + +The **Change History** tab allows the user to see a timeline of what has changed, when, and by whom. The **Recent Activity** section shows this in a more intuitive way. + +The **Comments** section displayed below the ticket allows anyone to leave a comment, which helps to speed up resolution of the ticket. + +### Edit Ticket + +The Edit ticket form is accessible by the ticket's Submitter and Staff. Possible edits include reassigning the ticket, upgrading/downgrading its Priority, editing Description, Tags, Summary. + +### Dashboard + +Dashboard page allows visual interaction with the tickets data across 6 interconnected charts. Clicking a segment on the row and pie charts or dragging to select a date range will filter all charts. Reset filters button will reset all charts. + +### KANBAN + +Tickets are displayed in KANBAN columns by Status: New, In Progress, Resolved, Cancelled. Given that Cancelled tickets may be unimportant to some users, the 'Hide Cancelled' checkbox + +### Team + +The Team page acts as an address book of users of the site. Users are split into Submitters and Staff. + +### Account + +The Account page is where users can view and edit their account information. Users can upload a profile picture from their device, add First and Last Names, add personal Zoom Meeting IDs. + +The user's account status (Basic or PRO) is displayed, including a 'Go PRO' button if basic user. + +Submitters can request Admin Access via a link. + +## Existing Features Users can: -- Feature 1: Submit a ticket - Bug or Feature Request. -- Feature 2: Edit their own ticket. Staff members can edit all tickets. -- Feature 3: View, search and filter list of tickets submitted by all users. -- Feature 4: View any ticket in detail, including Recent Activity. -- Feature 4: Upvote any ticket. -- Feature 5: Leave a comment on any ticket. -- Feature 6: View list of other users and their details. -- Feature 7: Update their own details, including First Name, Last Name, Zoom Meeting ID -- Feature 8: Upload a profile image. -- Feature 9: Derive ticket insights in interactive Dashboard view. -- Feature 10: View tickets in KANBAN View (PRO Feature). +- Submit a ticket - up to 10 tickets/Month if Basic User, unlimited if PRO User. +- Edit a ticket. +- View, search and filter all tickets in table view. +- View any ticket in further detail. +- Upvote any ticket. +- Leave a comment on any ticket. +- View list of other users and their details. +- Update their own details, including First Name, Last Name, Zoom Meeting ID +- Upload a profile image. +- Derive ticket insights in interactive Dashboard view. +- Visualise tickets in KANBAN View. +- Pay to upgrade account to PRO, which allows unlimited ticket submissions per month, and KANBAN view. -### Features Left to Implement +## Potential Future Features -- Potential Feature 1: Add filters in KANBAN View, including a toggle to show only My Tickets. -- Potential Feature 2: Limit upvotes to 1 per user per ticket. Add tooltip to upvote button listingj users who have upvoted. -- Potential Feature 3: Add phone number field to user profile with ability to click to call via 'callto:'. -- Potential Feature 4: Additional graphs in Dashboard view, including Age vs Priority bubble chart. -- Potential Feature 5: Make tickets in KANBAN View draggable, so that ticket status can be changed via dragging into other column. +- Add filters and searching to KANBAN View, including a toggle to show only My Tickets. +- Limit upvotes to 1 per user per ticket. Add tooltip to show which users who have upvoted - if more than 3 users, display eg. 'Joe and 4 others'. +- Add phone number field to user profile with ability to click to call via 'callto:'. +- Additional graphs in Dashboard view, including Age vs Priority bubble chart. +- Make tickets in KANBAN View draggable, so that ticket status can be changed via dragging into other column. +- Dark Mode setting toggle in Account page. +- Allow the Assignee to set an Estimated Resolved Date for each ticket. -## Database +# Database -Sqlite3 was used during development. For deployment, data was migrated to PostgreSQL. +Sqlite3 was used during development. For deployment, data tables and data was migrated to a PostgreSQL database. -### Data Models +## Data Models -#### Accounts App +### Accounts App User Model @@ -149,7 +267,7 @@ Profile Model | image | ImageField | Profile image, which is referred to throughout site. | | zoom_id | CharField | | -#### Tickets App +### Tickets App Ticket Model @@ -179,79 +297,83 @@ Comment Model Django REST Framework -An API was set up to form the tickets data source of the dashbord graphs. +An API was set up which is the data source of the dashboard graphs. -#### Checkout App +### Checkout App Order Model -| Field | Type | Description | -| :-------------- | :-------- | :---------- | -| full_name | CharField | | -| phone_number | CharField | | -| country | CharField | | -| postcode | CharField | | -| town_or_city | CharField | | -| street_address1 | CharField | | -| street_address2 | CharField | | -| county | CharField | | -| date | DateField | | +| Field | Type | +| :-------------- | :-------- | +| full_name | CharField | +| phone_number | CharField | +| country | CharField | +| postcode | CharField | +| town_or_city | CharField | +| street_address1 | CharField | +| street_address2 | CharField | +| county | CharField | +| date | DateField | OrderLineItem Model -| Field | Type | Description | -| :------- | :---------------- | :---------- | -| order | ForeignKey(Order) | | -| product | CharField | | -| quantity | IntegerField | | +| Field | Type | +| :------- | :---------------- | +| order | ForeignKey(Order) | +| product | CharField | +| quantity | IntegerField | -## Technologies Used +# Technologies -- [Autoprefixer CSS Online](https://autoprefixer.github.io/) : used to add vendor prefixes. -- [Balsamiq](https://balsamiq.com/) : used to create wireframes. -- [Bootstrap](https://bootstrap.com/) : used for responsive webpages. -- [Chrome Developer Tools](https://developers.google.com/web/tools/chrome-devtools) : used extensively to ensure device responsiveness. +- [Autoprefixer CSS Online](https://autoprefixer.github.io/) : to add vendor prefixes. +- [Balsamiq](https://balsamiq.com/) : to create wireframes. +- [Bootstrap](https://bootstrap.com/) : for responsive webpages. +- [Chrome Developer Tools](https://developers.google.com/web/tools/chrome-devtools) : to ensure device responsiveness. - [crossfilter](https://github.com/crossfilter/crossfilter) : enables filters to be applied to all graphs. - [CSS3](https://www.w3.org/Style/CSS/Overview.en.html) : styling language. - [d3js.org](https://d3.js) : Javascript charting library. -- [DataTables.net](https://datatables.net) : used for pagination and filtering of tables. -- [dc.js](https://dc.js) : charting Javascript libary built on d3.js. -- [Django Crispy Forms](https://django-crispy-forms.readthedocs.io/) : used for Django form styling. -- [Django REST Framework](https://www.django-rest-framework.org/) : used for as API data source for dashboard charts. -- [Django Simple History](https://django-simple-history.readthedocs.io) : used for tracking changes to model fields. -- [Git](https://git-scm.com/) : used for version control. +- [DataTables.net](https://datatables.net) : pagination and filtering of tables. +- [DBDiagram](https://dbdiagram.io/) : mapping database relationships. +- [DBeaver](https://dbeaver.io) : database tool to confirm successful data migration from sqlite3. +- [dc.js](https://dc.js) : charting Javascript library built on d3.js. +- [Django](https://django.io/) : +- [Django Crispy Forms](https://django-crispy-forms.readthedocs.io/) : for Django form styling. +- [Django REST Framework](https://www.django-rest-framework.org/) : API data source for dashboard charts. +- [Django Simple History](https://django-simple-history.readthedocs.io) : tracking changes to model fields. +- [Git](https://git-scm.com/) : version control. - [GitHub](https://github.com) : code repository and source branch used in deployment. -- [Google Fonts](https://fonts.google.com/) : used for placeholder. -- [Heroku](https://www.heroku.com) : used for deployment. -- [HTML5](https://www.w3.org/html) : used for page structure. -- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) : -- [jQuery](https://jquery.com/) : used to select and manipulate HTML elements. -- [Material Icons](https://material.io/) : used for icons and fonts. -- [Pillow](https://pillow.readthedocs.io) : used for proccessing images in database. -- [PostgreSQL](https://www.postgresql.org/) : used as relational database in production. -- [Stripe](https://stripe.com/) : used to accept payments. -- [Travis](https://travis-ci.org) : used for continuous integration. +- [Heroku](https://www.heroku.com) : deployment. +- [jQuery](https://jquery.com/) : manipulate HTML elements. +- [Material Icons](https://material.io/) : icons and fonts. +- [PEP8 Validator](http://pep8online.com/) : validation of Python code. +- [Pillow](https://pillow.readthedocs.io) : processing images in database. +- [PostgreSQL](https://www.postgresql.org/) : as relational database in production. +- [Stripe](https://stripe.com/) : accept payments. +- [Travis](https://travis-ci.org) : continuous integration. - [VSCode](https://code.visualstudio.com) : preferred code editor. -- [W3C Validator](https://jigsaw.w3.org) : used to validate HTML & CSS. +- [W3C Validator](https://jigsaw.w3.org) : validate HTML & CSS. -## Testing +# Testing Extensive automatic and manual testing was conducted to ensure the site functions and looks well on all major browsers (Chrome, Firefox, Safari, Edge) and device sizes. -### Automated Testing +## Unit Testing + +- Django testing framework was used. tests_xx.py files show the tests run. -- Django testing framework was used. +## Manual Testing -### Desktop Testing (Manual) +### Desktop Testing The following manual tests passed: -Index Page +Home +- If no user is logged in, 3 'Create Free Account' buttons are displayed - If Basic user is logged in, 3 'Go PRO' buttons are displayed. - If Pro user is logged in, 3 'Go PRO' buttons are hidden. -Tickets Page +Tickets - Global search input filters in all fields. - Reset filters button reloads page. @@ -263,25 +385,26 @@ Tickets Page - Row colours represent status. - Raise Ticket button takes user to Add Ticket page. -Dashboard Page +Dashboard - All 6 charts display with adequate padding. - All 6 charts be filtered on click, or range selection. - Display updates to show how many tickets are filtered - eg. 12 of 25 Tickets. -KANBAN Page +KANBAN - If user is not PRO user, redirect to Checkout page. - 'Hide Cancelled' checkbox toggles Cancelled column. - Ticket count and counts for each column (ticket status) are correct. -- Quick update status. +- Quick update dropdown updates status and success message displayed: 'Ticket status updated.' -Add Ticket page +Add Ticket - If Basic user, message is displayed shows how many tickets have been submitted out of 10 in the current month. If limit has been reached, message is displayed and user is redirected to checkout page. - Form is valid and submits even if Tags and Screenshot is blank. +- Success message is displayed: 'Ticket raised.' -View Ticket page +View Ticket - Jumbotron colour correctly represents ticket status. - Submitter and Assignee pill badges show user profile pictures if any, and on click a drop-right menu with 'mailto:' links Zoom links functioning. @@ -289,15 +412,16 @@ View Ticket page - On click, Quick Status dropdown links update the ticket status and display confirmation message. - Edit ticket button takes user to the Edit Ticket form. - If screenshot exists, following link shows the screenshot in a modal. Download button downloads image. -- Comments can be submitted. +- Comments can be submitted and success message is displayed: 'Comment shared.' -Edit Ticket page +Edit Ticket - Only submitter or staff can edit a ticket. - Fields are pre-filled with existing values. - Submitting form updates all fields. +- On successful edit, success message is displayed: 'Ticket successfully updated'. -Account page +Account - User Profile image can be changed via upload. Image is displayed with rounded border. - First Name, Last Name can be updated via edit button. @@ -311,7 +435,7 @@ Account page - Return home button takes user back to Home Page - Navigation buttons function -### Mobile and Tablet Tests (Manual) +### Mobile and Tablet Testing The above Desktop Tests were also conducted on mobile and tablet devices (via Chrome DevTools). In addition, the following mobile and tablet-specific tests were run: @@ -325,44 +449,72 @@ The following tests failed: | :-------- | :---------------------------------------------- | :-------------------------------------- | :-------- | :---------------------------------------------------------------------- | | 1 | Content is not squeezed or overlapping (Mobile) | Tickets table overflowing horizontally. | Yes | Added Bootstrap class 'table-responsive' to enable horizontal scrolling | -### Code Validation +## Code Validation -| Code | Result | -| :-------------------------------------------------------------- | :----- | -| CSS ([W3C](https://jigsaw.w3.org/css-validator/)) | PASS | -| HTML ([W3C](https://validator.w3.org/)) | PASS | -| Javascript with no major errors ([jshint](https://jshint.com/)) | PASS | -| Python ([jshint](https://pep8online.com/)) | PASS | +| Code | Result | +| :---------------------------------------------------------- | :----- | +| CSS ([W3C](https://jigsaw.w3.org/css-validator/)) | PASS | +| HTML ([W3C](https://validator.w3.org/)) | PASS | +| Javascript: no major errors ([jshint](https://jshint.com/)) | PASS | +| Python ([PEP8](https://pep8online.com/)) | PASS | ^ The following classes of errors were deemed not applicable, as the validator did not take into account Flask and Jinja templating: -## Deployment +# Deployment -### Heroku +## Heroku The application was deployed to Heroku, via the following steps: 1. Ensure `requirements.txt` reflects all dependencies via `pip freeze > requirements.txt` -2. Create `Procfile` via `echo web: python app.py > Procfile` -3. `git add` above files, then commit and push to GitHub. -4. Heroku.com > Create new app > App name: django-issue-tracker-1, Region: Europe +2. Create `Procfile` via `web: gunicorn issue_tracker.wsgi:application > Procfile` +3. `git add` above files, then commit and push to GitHub +4. Heroku.com > Create new app > App name: 'django-issue-tracker-1' (app name must be unique), Region: Europe 5. Deploy > Deployment method > Link GitHub account 6. Select repository 'milestone-4' 7. Select branch: 'master' 8. Set Config Vars: Heroku Settings > Config Vars: -| Config Var | Key | -| :-------------------- | :---------------------- | -| DATABASE_URL | 'postgres database url' | -| DISABLE_COLLECTSTATIC | 1 | -| SECRET_KEY | 'your secret key' | -| STRIPE_PUBLISHABLE | 'from stripe' | -| STRIPE_SECRET | 'from stripe' | +| Config Var | Key | +| :-------------------- | :------------------------------------ | +| AWS_ACCESS_KEY_ID | \ | +| AWS_SECRET_ACCESS_KEY | \ | +| DATABASE_URL | \ | +| DISABLE_COLLECTSTATIC | 1 | +| HOSTNAME | \.herokuapp.com | +| SECRET_KEY | \ | +| STRIPE_PUBLISHABLE | \ | +| STRIPE_SECRET | \ | 9. Manual Deploy > Deploy Branch (master) 10. Heroku Website > Open App -### Local Deployment +### Data Migration + +To ensure a rich dataset for assessment purposes, testing data (including users, tickets) was migrated from sqlite3 to the PostgreSQL database using the following method: + +Dump existing data into json format: + +`python3 manage.py dumpdata > datadump.json` + +Change DATABASES in settings.py to Postgres, then migrate: + +`python3 manage.py migrate --run-syncdb` + +Exclude contenttype data: + +``` +python3 manage.py shell +>>> from django.contrib.contenttypes.models import ContentType +>>> ContentType.objects.all().delete() +>>> quit() +``` + +Load json data into PostgreSQL: + +`python3 manage.py loaddata datadump.json` + +## Local Deployment 1. 'Clone or download' repository from https://github.com/tdunn891/milestone-4, or from command line: @@ -384,17 +536,9 @@ The application was deployed to Heroku, via the following steps: `touch env.py` - Import os at top of file, then set each environment variable default: - - `os.environ.setdefault("",)` + 'import os' at top of file, then set each environment variable under the Heroku heading above like so: -| Config Var | -| :-------------------- | -| DATABASE_URL | -| DISABLE_COLLECTSTATIC | -| SECRET_KEY | -| STRIPE_PUBLISHABLE | -| STRIPE_SECRET | + `os.environ.setdefault("","")` 6. Run app: @@ -402,20 +546,36 @@ The application was deployed to Heroku, via the following steps: 7. Go to Local Host in browser to view: - `http://127.0.0.1:8000/` + `http://localhost:8000/` -## Credits +## Development vs Production Versions -### Content +| | Development | Production | +| :--------------------------------------------------------- | :---------------- | :----------------------------------------------------------------- | +| Database | Sqlite3 | [PostgreSQL](https://www.postgresql.org/) | +| Environment Variables | Local env.py file | Heroku Config Vars | +| Hostname | localhost:8000 | https://dashboard.heroku.com/apps/django-issue-tracker-1 | +| Media Files (User-uploaded screenshots and profile images) | Local | [AWS S3](https://aws.amazon.com/s3/) | +| Static Files (CSS, Javascript, site images) | Local | [AWS S3](https://aws.amazon.com/s3/) | +| Web Host | localhost | [Heroku](https://dashboard.heroku.com/apps/django-issue-tracker-1) | + +# Credits + +## Content All icons sourced from [Material Icons](https://material.io/resources/icons). -### Media +## Media + +'Trusted By' company logo images sourced from [Jira Atlassian](https://www.atlassian.com/software/jira) + +## Acknowledgements -Images +YouTube Channels: -### Acknowledgements +- [Pretty Printed](https://www.youtube.com/channel/UC-QDfvrRIDB6F0bIO4I4HkQ) +- [Max Goodridge](https://www.youtube.com/user/Max204204204/) -YouTube Tutorials: Pretty Printed +Big thanks to my mentor, Brian M., friends and family for help with testing and feedback. -Big thanks to friends and family for help with testing and feedback. +###### Disclaimer: Project created for educational purposes only diff --git a/accounts/templates/index.html b/accounts/templates/index.html index 1b673ba..816efb8 100644 --- a/accounts/templates/index.html +++ b/accounts/templates/index.html @@ -1,7 +1,6 @@ {% extends 'base.html' %} {% load static from static %} {% block title %}TrackIt | Home{% endblock %} -{% block page_heading %}{% endblock %} {% block content %}
@@ -17,7 +16,8 @@

Issue and Feature tracking done right

{% if not user.is_authenticated %}

Get started today:

- person_add Create Free Account + person_add Create Free Account +

Returning Users:

@@ -47,9 +47,9 @@

Issue and Feature tracking done right


-

- build - Features

+

+ build Features +


@@ -109,7 +109,12 @@



- Customer Logos + Customer Logos + Customer Logos Mobile + Customer Logos Mobile 2

@@ -123,8 +128,7 @@

Plans


-
- +
Get Started @@ -155,7 +159,7 @@
FREE
-
+
stars @@ -189,7 +193,6 @@
€9.90
{% endif %}
-
diff --git a/accounts/templates/profile.html b/accounts/templates/profile.html index 53c8a69..4f92b61 100644 --- a/accounts/templates/profile.html +++ b/accounts/templates/profile.html @@ -1,7 +1,10 @@ {% extends 'base.html' %} {% load crispy_forms_tags %} {% block title %}TrackIt | My Account{% endblock %} -{% block page_heading %}account_circle My Account +{% block page_heading %} +

+ account_circle My Account +


{% endblock %} {% block container-class %}profile-container{% endblock %} @@ -10,8 +13,8 @@
{% if user.profile.image %} - Profile Image + Profile Image {% else %} account_circle @@ -51,7 +54,7 @@
{% csrf_token %} + value="{{ user.first_name }}" />
@@ -69,7 +72,7 @@
{% csrf_token %} - +
@@ -83,8 +86,8 @@ {% if user.profile.zoom_id %} - + {{ user.profile.zoom_id }} {% endif %} diff --git a/accounts/templates/user_list.html b/accounts/templates/user_list.html index 635638c..2d0e12d 100644 --- a/accounts/templates/user_list.html +++ b/accounts/templates/user_list.html @@ -1,12 +1,16 @@ {% extends 'base.html' %} +{% block head %} + + +{% endblock %} {% block title %}Trackit | Team{% endblock %} -{% block page_heading %}people Users ({{ users.count }}) +{% block page_heading %} +

+ people Users ({{ users.count }}) +


{% endblock %} -{% block head %} - -{% endblock %} {% block container-class %}user-list-container{% endblock %} {% block content %} @@ -61,7 +65,7 @@ {% if submitter.profile.zoom_id %} - {{ submitter.profile.zoom_id }} @@ -97,7 +101,7 @@ {% if staff_member.profile.image %} - Profile Image {% else %} @@ -115,7 +119,7 @@ {% if staff_member.profile.zoom_id %} - {{ staff_member.profile.zoom_id }} @@ -133,6 +137,7 @@ {% endblock %} {% block scripts %} - + + {% endblock %} diff --git a/accounts/urls.py b/accounts/urls.py index a41f1d4..a95c762 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -1,6 +1,8 @@ from django.conf.urls import url from . import views -from accounts.views import index, logout, login, registration, user_profile, user_list, update_first_name, update_last_name, update_zoomid, grant_staff_access +from accounts.views import (index, logout, login, registration, user_profile, + user_list, update_first_name, update_last_name, + update_zoomid, grant_staff_access) urlpatterns = [ url(r'^logout/$', logout, name='logout'), diff --git a/accounts/views.py b/accounts/views.py index 799af56..3e21dd4 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -145,8 +145,8 @@ def update_zoomid(request, pk): def grant_staff_access(request, pk): """Allows Code Institute assessors to be immediately granted Staff Access. This is to ensure Assessors can see the full feature set. - In a non-assessment situation, clicking Request Staff Access link would send email to Admin, - who could then set is_staff to True + In a non-assessment situation, clicking Request Staff Access link would + send email to Admin, who could then set is_staff to True. """ user = get_object_or_404(User, pk=pk) if request.method == 'POST': @@ -157,5 +157,6 @@ def grant_staff_access(request, pk): user.is_staff = True user.save() messages.success( - request, "You have been granted Staff Access as a CI Assessor. You can now edit all tickets.") + request, ("You have been granted Staff Access as a CI Assessor." + "You can now edit all tickets.")) return redirect(user_profile) diff --git a/checkout/forms.py b/checkout/forms.py index 38f3169..a7497e8 100644 --- a/checkout/forms.py +++ b/checkout/forms.py @@ -24,12 +24,16 @@ class Meta: model = Order widgets = { 'full_name': forms.TextInput(attrs={'placeholder': 'Full Name'}), - 'street_address1': forms.TextInput(attrs={'placeholder': 'Street Address (line 1)'}), - 'street_address2': forms.TextInput(attrs={'placeholder': 'Street Address (line 2)'}), - 'town_or_city': forms.TextInput(attrs={'placeholder': 'Town/City'}), + 'street_address1': forms.TextInput( + attrs={'placeholder': 'Street Address (line 1)'}), + 'street_address2': forms.TextInput( + attrs={'placeholder': 'Street Address (line 2)'}), + 'town_or_city': forms.TextInput( + attrs={'placeholder': 'Town/City'}), 'county': forms.TextInput(attrs={'placeholder': 'County'}), 'country': forms.TextInput(attrs={'placeholder': 'Country'}), - 'phone_number': forms.TextInput(attrs={'placeholder': 'Phone Number'}), + 'phone_number': forms.TextInput( + attrs={'placeholder': 'Phone Number'}), 'postcode': forms.TextInput(attrs={'placeholder': 'Postcode'}) } fields = ( diff --git a/checkout/templates/checkout.html b/checkout/templates/checkout.html index 2bc3dc8..b75e342 100644 --- a/checkout/templates/checkout.html +++ b/checkout/templates/checkout.html @@ -4,19 +4,21 @@ {% block title %}TrackIt | Checkout | Go PRO{% endblock %} {% block head %} - - + - + {% endblock head %} -{% block page_heading %} - shopping_cart - Checkout
-
{% endblock %} +{% block page_heading %} +

+ shopping_cart Checkout +

+
+{% endblock %} {% block container-class %}checkout-container{% endblock %} {% block content %}
@@ -37,7 +39,7 @@

Go PRO


Total Due: 9.90 EUR (Single Payment)

-
+ {% csrf_token %}
@@ -92,17 +94,19 @@

Go PRO

{{ payment_form.cvv | as_crispy_field }} + {{ payment_form.stripe_id | as_crispy_field }}

- {{ payment_form.stripe_id | as_crispy_field }} -
- - Cancel +
+
+ + Cancel +
+
-
{% endblock %} diff --git a/checkout/test_models.py b/checkout/test_models.py deleted file mode 100644 index a2feab2..0000000 --- a/checkout/test_models.py +++ /dev/null @@ -1,2 +0,0 @@ -from django.test import TestCase -from .models import Order, OrderLineItem diff --git a/checkout/tests.py b/checkout/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/checkout/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/checkout/views.py b/checkout/views.py index 960ce5c..61f8fe1 100644 --- a/checkout/views.py +++ b/checkout/views.py @@ -50,7 +50,6 @@ def checkout(request): user.save() messages.success( request, "You have successfully paid. Enjoy PRO!") - # request.session['cart'] = {} return redirect(reverse('profile')) else: @@ -63,4 +62,7 @@ def checkout(request): payment_form = MakePaymentForm() order_form = OrderForm() - return render(request, "checkout.html", {'order_form': order_form, 'payment_form': payment_form, 'publishable': settings.STRIPE_PUBLISHABLE}) + return render(request, "checkout.html", + {'order_form': order_form, + 'payment_form': payment_form, + 'publishable': settings.STRIPE_PUBLISHABLE}) diff --git a/custom_storages.py b/custom_storages.py index c4f691d..2695816 100644 --- a/custom_storages.py +++ b/custom_storages.py @@ -4,3 +4,7 @@ class StaticStorage(S3Boto3Storage): location = settings.STATICFILES_LOCATION + + +class MediaStorage(S3Boto3Storage): + location = settings.MEDIAFILES_LOCATION diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..7371aee Binary files /dev/null and b/db.sqlite3 differ diff --git a/issue_tracker/settings.py b/issue_tracker/settings.py index 07d4c76..8654a6c 100644 --- a/issue_tracker/settings.py +++ b/issue_tracker/settings.py @@ -1,6 +1,5 @@ """ Django settings for issue_tracker project. - Generated by 'django-admin startproject' using Django 1.11.24. """ @@ -16,10 +15,8 @@ print('DEVELOPMENT=FALSE') -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -# SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.environ.get('SECRET_KEY') # DEBUG = development @@ -36,7 +33,7 @@ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', - 'livereload', + # 'livereload', 'django.contrib.staticfiles', 'django_forms_bootstrap', 'accounts', @@ -58,7 +55,7 @@ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'simple_history.middleware.HistoryRequestMiddleware', - 'livereload.middleware.LiveReloadScript', + # 'livereload.middleware.LiveReloadScript', ] ROOT_URLCONF = 'issue_tracker.urls' @@ -82,23 +79,23 @@ WSGI_APPLICATION = 'issue_tracker.wsgi.application' # Database -# https://docs.djangoproject.com/en/1.11/ref/settings/#databases -if development: - print('DATABASE: SQLITE3') + +if "DATABASE_URL" in os.environ and development is False: + print('Database URL found. Using POSTGRESQL') + DATABASES = { + 'default': dj_database_url.parse(os.environ.get('DATABASE_URL')) + } +else: + print("Database URL not found. Using SQLite instead.") DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } -else: - print('DATABASE: POSTGRESQL') - DATABASES = { - 'default': dj_database_url.parse(os.environ.get('DATABASE_URL')) - } + # Password validation -# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { @@ -117,7 +114,6 @@ # Internationalization -# https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -131,7 +127,6 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.11/howto/static-files/ AWS_S3_OBJECT_PARAMETERS = { 'Expires': 'Thu, 31 Dec 2099 20:00:00 GMT', @@ -142,6 +137,7 @@ AWS_S3_REGION_NAME = 'eu-west-1' AWS_ACCESS_KEY = os.environ.get("AWS_ACCESS_KEY_ID") AWS_SECRET_ACCESS = os.environ.get("AWS_SECRET_ACCESS_KEY") + AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME STATICFILES_LOCATION = 'static' @@ -150,20 +146,20 @@ STATIC_URL = '/static/' STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'), ) -# STATIC_ROOT = (os.path.join(BASE_DIR, 'staticfiles')) - -MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" - -TAGGIT_CASE_INSENSITIVE = True +MEDIAFILES_LOCATION = 'media' +DEFAULT_FILE_STORAGE = 'custom_storages.MediaStorage' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') -MEDIA_URL = '/media/' +MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION) CRISPY_TEMPLATE_PACK = 'bootstrap4' STRIPE_PUBLISHABLE = os.getenv('STRIPE_PUBLISHABLE') STRIPE_SECRET = os.getenv('STRIPE_SECRET') +TAGGIT_CASE_INSENSITIVE = True + +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" MESSAGE_TAGS = { messages.DEBUG: 'alert-info', messages.INFO: 'alert-info', diff --git a/issue_tracker/urls.py b/issue_tracker/urls.py index 3a015c2..14250fa 100644 --- a/issue_tracker/urls.py +++ b/issue_tracker/urls.py @@ -1,24 +1,12 @@ -"""issue_tracker URL Configuration +"""issue_tracker URL Configuration""" -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.11/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" from django.conf.urls import url, include from django.contrib import admin from django.conf import settings from django.conf.urls.static import static from accounts.views import index + urlpatterns = [ url(r'^$', index, name='index'), url(r'^admin/', admin.site.urls), diff --git a/issue_tracker/wsgi.py b/issue_tracker/wsgi.py index 742b278..e337904 100644 --- a/issue_tracker/wsgi.py +++ b/issue_tracker/wsgi.py @@ -2,9 +2,6 @@ WSGI config for issue_tracker project. It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ """ import os diff --git a/requirements.txt b/requirements.txt index d3f0aaa..dc64d47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,14 @@ asgiref==3.2.5 autopep8==1.5 -beautifulsoup4==4.8.2 boto3==1.12.37 botocore==1.15.37 certifi==2019.11.28 chardet==3.0.4 coverage==5.0.4 dj-database-url==0.5.0 -Django==1.11.24 +Django==1.11.28 django-crispy-forms==1.9.0 django-forms-bootstrap==3.1.0 -django-livereload-server==0.3.2 django-simple-history==2.8.0 django-storages==1.9.1 django-taggit==1.2.0 @@ -27,7 +25,6 @@ pytz==2019.3 requests==2.23.0 s3transfer==0.3.3 six==1.14.0 -soupsieve==2.0 sqlparse==0.3.1 stripe==2.43.0 tornado==6.0.3 diff --git a/static/css/style.css b/static/css/style.css index 5481f65..e396d25 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,3 +1,5 @@ +/* prefixed by https://autoprefixer.github.io (PostCSS: v7.0.26, autoprefixer: v9.7.3) */ + html { height: 100%; scroll-behavior: smooth; @@ -21,6 +23,8 @@ html { /* Nav-item hover */ .nav-item:hover { + -webkit-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; } @@ -29,6 +33,10 @@ html { margin: auto; } +#login-nav-text { + font-size: 1rem; +} + /* Default body font size */ body { font-size: 15px; @@ -45,6 +53,11 @@ main { margin-top: 66px; } +/* Messages Row Alert */ +.messages-row .col .alert { + margin-bottom: 0; +} + /* Index page section headings */ h4 { color: #0052cc; @@ -61,6 +74,10 @@ h4 { color: #0093e9; } +#account-profile-thumbnail { + width: 38px; +} + /* Container max widths */ .tickets-container { max-width: 1564px; @@ -121,10 +138,6 @@ h4 { font-size: 15px; } -/* Index page Plans cards */ -.card-deck .card { -} - #customer-logos { width: 76%; } @@ -190,7 +203,7 @@ g .row { /* Dashboard Filtered Count */ #filtered-count { - font-size: 16px; + font-size: 17px; } .clickable-row:hover { @@ -293,6 +306,14 @@ footer { /* Used for Navbar colour gradient */ .bg-royal-blue { + background-image: -webkit-gradient( + linear, + left bottom, + left top, + from(#30cfd0), + to(#330867) + ); + background-image: -o-linear-gradient(bottom, #30cfd0 0%, #330867 100%); background-image: linear-gradient(to top, #30cfd0 0%, #330867 100%); background-color: #0093e9; } @@ -309,6 +330,10 @@ footer { background-color: #f1f1f1 !important; } +.bg-light-blue { + background-color: lightblue !important; +} + /* Kanban headers */ .jumbotron h5 { padding: 8px; @@ -319,6 +344,7 @@ footer { #ticket-search-boxes th input { width: 100%; padding: 0px; + -webkit-box-sizing: border-box; box-sizing: border-box; } @@ -353,8 +379,13 @@ footer { padding: 0.5rem 1px; } /* customer logos */ - #customer-logos { - width: 100%; + #customer-logos, + #customer-logos-mobile { + width: 98%; + } + #customer-logos-mobile-2 { + margin-top: 34px; + width: 65%; } } @@ -362,6 +393,8 @@ footer { @media (max-width: 568px) { .pagination { margin-top: 20px !important; + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; justify-content: center !important; } .table-md td, diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 9545a3b..0a104e8 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -1,10 +1,11 @@ -// Fetch data from Django REST API -fetch('http://localhost:8000/tickets/api/tickets') +// Fetch source data for charts from Django REST API +fetch('https://django-issue-tracker-1.herokuapp.com/tickets/api/tickets') + // fetch('http://localhost:8000/tickets/api/tickets') .then((response) => { return response.json(); }) .then((data) => { - // Date parsing + // Parse datetime const dateFormatSpecifier = '%Y-%m-%dT%H:%M:%S.%f%Z'; const dateFormatParser = d3.timeParse(dateFormatSpecifier); @@ -17,7 +18,7 @@ fetch('http://localhost:8000/tickets/api/tickets') d.created_date_month = d3.timeMonth(d.created_date_dd); // Month d.created_date_year = d3.timeYear(d.created_date_dd); - // RESOLVED DATE parsed, if resolved + // If ticket status is resolved, parse RESOLVED DATE if (d.resolved_date) { // Parsed date d.resolved_date_dd = dateFormatParser(d.resolved_date); @@ -33,6 +34,7 @@ fetch('http://localhost:8000/tickets/api/tickets') }); function drawGraphs(data) { + // Crossfilter data let ndx = crossfilter(data); // Pass crossfiltered data to charts @@ -43,16 +45,18 @@ function drawGraphs(data) { drawStatusByMonthBarChart(ndx); showFilteredCount(ndx); + // Render all charts dc.renderAll(); } -// Status by month +// Status by Month Bar Chart function drawStatusByMonthBarChart(ndx) { let dateCreatedDim = ndx.dimension((d) => d.created_date_day); let statusGroup = dateCreatedDim .group() .reduce(reduceAdd, reduceRemove, reduceInitial); + // Custom reducer function reduceAdd(i, d) { i[d.status] = (i[d.status] || 0) + 1; return i; @@ -137,7 +141,7 @@ function drawStatusRowChart(ndx) { let statusDim = ndx.dimension((d) => d.status); let statusGroup = statusDim.group(); - // Open/Closed + // Open/Closed Dimension let openClosedDim = ndx.dimension(function (d) { if (d.status == 'Resolved' || d.status == 'Cancelled') { return 'Closed'; @@ -145,6 +149,8 @@ function drawStatusRowChart(ndx) { return 'Open'; } }); + + // Open/Closed Group let openClosedGroup = openClosedDim.group(); // Status Row Chart diff --git a/templates/base.html b/templates/base.html index 7e91b02..8fe5313 100644 --- a/templates/base.html +++ b/templates/base.html @@ -10,24 +10,13 @@ - - - - - - - + - + {% block head %} {% endblock %} - + @@ -71,8 +60,8 @@ {% if user.profile.image %} - My Profile Picture + My Profile Picture {% else %} account_circle {% endif %} @@ -93,7 +82,7 @@ input - Log In + Log In person_addCreate Free Account @@ -121,7 +110,7 @@

-

{% block page_heading %}{% endblock %}

+ {% block page_heading %}{% endblock %}
{% block content %} @@ -135,19 +124,31 @@

{% block page_heading %}{% endblock %}

+ + + + + + + + {% block scripts %} diff --git a/tickets/forms.py b/tickets/forms.py index 10cdb43..200f42a 100644 --- a/tickets/forms.py +++ b/tickets/forms.py @@ -19,8 +19,9 @@ class Meta: model = Ticket widgets = { 'summary': forms.TextInput(attrs={'placeholder': 'Summary'}), - 'description': forms.Textarea(attrs={'placeholder': 'Add a description', - 'rows': 4}), + 'description': forms.Textarea( + attrs={'placeholder': 'Add a description', + 'rows': 4}), } fields = ('ticket_type', 'summary', 'description', 'priority', @@ -39,7 +40,11 @@ class Meta: model = Ticket widgets = { 'summary': forms.TextInput(attrs={'placeholder': 'Summary'}), - 'description': forms.Textarea(attrs={'placeholder': 'Add a description', 'rows': 4}), + 'description': forms.Textarea( + attrs={ + 'placeholder': 'Add a description', + 'rows': 4 + }), } fields = ('ticket_type', 'summary', 'description', 'priority', @@ -52,7 +57,8 @@ class Meta: model = Comment fields = ('comment_body',) widgets = { - 'comment_body': forms.Textarea(attrs={'placeholder': 'Leave a comment', 'rows': 2}) + 'comment_body': forms.Textarea( + attrs={'placeholder': 'Leave a comment', 'rows': 2}) } labels = { 'comment_body': '', diff --git a/tickets/models.py b/tickets/models.py index 866b823..c2282b0 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -40,7 +40,9 @@ def age(self): def days_to_resolve(self): if self.resolved_date: - return int((self.resolved_date.date() - self.created_date.date()).days) + return int( + (self.resolved_date.date() - self.created_date.date()).days + ) else: return None diff --git a/tickets/templates/add_ticket.html b/tickets/templates/add_ticket.html index e08bcf8..758556d 100644 --- a/tickets/templates/add_ticket.html +++ b/tickets/templates/add_ticket.html @@ -6,7 +6,9 @@ {% endblock %} {% block page_heading %} -add Add Ticket +

+ add Add Ticket +


{% endblock %} {% block container-class %}add-ticket-container{% endblock %} diff --git a/tickets/templates/dashboard.html b/tickets/templates/dashboard.html index a6ceb9b..f094f05 100644 --- a/tickets/templates/dashboard.html +++ b/tickets/templates/dashboard.html @@ -2,18 +2,17 @@ {% load static from staticfiles %} {% load bootstrap_tags %} {% block head %} - - - + integrity="sha256-uq/xxnkXyjSgp47vyRtSvBEwWuxTFbtxbKwOxkmWIJM=" crossorigin="anonymous"> {% endblock %} {% block container-class %}dashboard-container{% endblock %} {% block title %}TrackIt | Dashboard{% endblock %} {% block page_heading %} -assessment Dashboard
+

+ assessment Dashboard +


{% endblock %} {% block content %} @@ -58,6 +57,9 @@
In-Demand Tickets
{% endblock %} {% block scripts %} + + + diff --git a/tickets/templates/edit_ticket.html b/tickets/templates/edit_ticket.html index 13900bd..1700e02 100644 --- a/tickets/templates/edit_ticket.html +++ b/tickets/templates/edit_ticket.html @@ -6,7 +6,9 @@ {% endblock %} {% block page_heading %} -edit Edit Ticket | {{ ticket.id }} +

+ edit Edit Ticket | {{ ticket.id }} +


{% endblock %} {% block container-class %}edit-ticket-container{% endblock %} diff --git a/tickets/templates/kanban.html b/tickets/templates/kanban.html index 093b514..07e31f5 100644 --- a/tickets/templates/kanban.html +++ b/tickets/templates/kanban.html @@ -3,8 +3,6 @@ {% block title %}TrackIt | Kanban{% endblock %} {% block container-class %}kanban-container{% endblock %} {% block page_heading %} -view_week Kanban ({{ tickets|length }}) -

@@ -14,15 +12,19 @@
Hide Cancelled ({{ cancelled_tickets|length}})
-
+

+ view_week Kanban ({{ tickets|length }}) +

{% endblock %} {% block content %} +
-
local_activity New - ({{ new_tickets|length }})
+
local_activity + New ({{ new_tickets|length }}) +
{% for ticket in new_tickets %}
@@ -31,18 +33,9 @@
title="{{ ticket.description }}">{{ ticket.summary }} - - - {{ ticket.priority }} - + + + {{ ticket.assigned_to }}
@@ -55,6 +48,20 @@
data-placement="left">build {% endif %} + + + {{ ticket.priority }} + + + {% if ticket.tags.names %} @@ -67,8 +74,9 @@
{% if request.user == ticket.submitted_by or request.user.is_staff %}
-
{% endif %} +
{% endfor %} @@ -102,30 +111,35 @@
title="{{ ticket.description }}">{{ ticket.summary }} - - - {{ ticket.priority }} - + + {{ ticket.assigned_to }} +
{% if ticket.ticket_type == "Bug" %} - bug_report {% elif ticket.ticket_type == "Feature" %} - build {% endif %} + + + {{ ticket.priority }} + + + {% if ticket.tags.names %} @@ -138,8 +152,9 @@
{% if request.user == ticket.submitted_by or request.user.is_staff %}
-