View the live site here.
Magical Places - London is site for sharing recommendations on hidden gems - the sometimes under-the-radar places that inspire joy and wonder.
It’s aimed at people that, for example, have a free afternoon and are looking for something inspiring to do; they can quickly look at the map at which interesting places are near, and save a favourites list to remind them of what to see in the future.
The goals for the site’s functionality were:
- Map view: users can view all ‘places’ on a map on the home page.
- User authentication: users can create an account, sign in and have permissions to edit and delete the comments that they create.
- Commenting: users have the ability to create, read, update and delete (CRUD) comments.
- Favouriting: users can favourite and unfavourite a place, and view a list of their favourite places.
- Create place: users can create a ‘place’ by filling out an autocomplete form powered by the Google Places API.
A Kanban board in Github projects was used for the Agile development process - see the board here.
‘Epics’ were broken down into ‘User Stories’, which were further broken down into ‘Tasks’.
Low-fidelity wireframes were used to test the design before building the site.
Home page:
List page:
Detail page (mobile):
Detail page (tablet):
Add a Place page:
note: a separate 'Favourite' model was created for the purpose of being able to sort the users Favourites chronologically.
One of the biggest challenges faced while building this site is the implementation of the Google Places photos.
The first approach was to save to the database a URL obtained with the getURL()
method.
That problem in this approach is that these URLs would expire after a few days, giving a 403 error message when trying to display the photo.
Google photo_reference
data is no longer supplied with the JavaScript API, and there are new Google terms and conditions that don’t allow a photo_reference
to be cached, and instruct the developer get a new link every time, using a PlaceDetails
request.
I then had to design a JavaScript system that used the Google place_id
stored in the database to send a new PlaceDetails
request every time the page is loaded to get the photos, and dynamically add them to the page.
I used a loading-spinner div (without the spinner) to cover a section of the page as the photos were being fetched.
Add a Place functionality - ‘place-add-form.js’
The search field in the Add a Place page uses the Google Autocomplete class and the Place Details service:
Home page - ‘script.js’
The Places objects are sent to the HTML template as a JSON array of objects. JavaScript picks them up and uses the google_place_id
to send requests to Google Places API for the photos. It then re-attaches each newly fetched photo to the corresponding Place object using the id
as a reference.
The colour scheme was chosen to give an earthy and inviting impression to the user.
Contrast accessibility was checked for with Eightshapes Contrast Grid and some of the originally chosen colours were darkened to improve their contrast ratio.
('Does Not Pass' combinations are hidden in this example)
It was used in the light 300 weight, and the regular 400 weight. It was chosen for its strong personality yet good readability.
The custom appearance of the map was created with Map Styles in Google Maps Platform on the Google Cloud Console. The colours were chosen from the site’s colour scheme.
The custom pin appearance was created in the site’s main red (#BF553B), directly in the JavaScript code:
const customPin = new PinElement({ background: "#BF553B", scale: 0.9, borderColor: "#fff", glyphColor: "#fff", });
The main map page, where users can click on map pins to see an info box with photo and place name:
The ‘Add’ icon is on the bottom right is only shown for logged in users.
The display or either ’Log In’ or ‘Log Out’ icons in the top right reflect the users state.
Where users can add a place by typing in to a search field that uses the Google Places Autocomplete to suggest existing places. Once the user clicks on a suggested place, the hidden form is automatically filled in by JavaScript, from the data provided by the Google Places API.
notes:
- The ability for a user to edit or delete a Place that they have added wasn't implemented:
- As the form data is not user-generated, and rather supplied by the Google Places API, there is a low chance or user error that would justify ‘edit’ functionality.
- If the user had the ability to delete places that were already commented on, other users would lose their comments and lose access to the place if it was in their favourites list.
- A fallback image was added for places that don’t have a corresponding Google image.
Places can be favourited/unfavourited by clicking on the heart icon.
The user can sort the list by ‘Newest’, ‘A-Z’ and ‘My Favourites’.
If a user doesn’t have any favourites yet, this message is displayed:
Sorted by ‘Favourites’:
The detail page is where the user can:
- create, read, update and delete a comment
- favourite/unfavourite a place
- view the place address
- view the number of comments
Desktop layout:
Mobile layout:
- Pagination: add pagination to the list page and detail page comments.
- Geolocation: add ‘locate user on the map’ functionality.
- New list-page sort option: sort by most commented place.
- Password reset: add ability to reset password.
- Social sign-in: allow users to sign in with their Google/Apples/etc. accounts.
- Google Places API (new): update to the new version of the API.
- Google ‘place_ids’ can, in theory, expire; a system would be needed for if this happens.
- User’s created places: allow users to view a list or map of all the places they have created.
- User profiles: a user page with a list of places created and comments left, either private to the user or publicly accessible.
- Jest testing: create a suite of JavaScript test using JEST.
In general, the code could easily be repurposed to create similar sites with a different focus in the places being shared (e.g. cycling-cafes/kids activities/rock-climbing centres etc.).
- Languages:
- Python
- JavaScript
- HTML5
- CSS3
- Framework:
- Django
- Database:
- PostgreSQL
- Visual Studio Code - as the code editor.
- Git - for version control, using the Gitpod IDE.
- GitHub - for storing the project.
- ElephantSQL - PostgreSQL as a service.
- Heroku - to deploy the application.
- Cloudinary - to host the static files.
- Chrome Developer Tools - to test responsiveness, edit CSS code, debug JavaScript and generate Lighthouse reports.
- Google Fonts - to import the site font, ‘Oswald’.
- Figma - to create the wireframes.
- Font Awesome - for all the site icons.
- Gauger Fonticon - for the favicon.
- Coolers - for an overview of the chosen colour palette.
- Am I Responsive - to create the responsive demo image at the top of the Readme.
- Excalidraw - to create the navigation diagram.
- Lucidchart - to create the database schemas.
- TinyPNG - to compress the Readme images.
- Quicktime - to record the screen capture for GIFs in the readme.
- Ezgif - to convert the Readme GIFs.
- Quillbot - for rephrasing demo comments.
- WebAIM WAVE - for automated testing of accessibility.
- WebAIM Contrast Checker - to check colour contrast accessibility.
- Eightshapes Contrast Grid - to visualise the contrast accessibility of the whole site colour palette.
- Code Institute’s Python Linter - for automated testing of the Python code.
- JSHint - to test the JavaScript code.
- W3C Markup Validator - to test the HTML code.
- W3C CSS Validator - to test the CSS code.
- Google Maps JavaScript API - to run the home page map.
- Google Places API - to get details and photos from place searches.
- django-allauth - for user authentication.
- gunicorn - as the HTTP server that allows Django to run on Heroku.
- psycopg2 - as the PostgreSQL database adapter for Python.
- dj_database_url - to allow the use of the
DATABASE_URL
environment variable inside Django. - Coverage.py - to measure test coverage of the Python code.
Python Unit Tests
Extensive unit tests were written for the Python code in Django, achieving a 94% coverage.
Python Linting
Python linting was carried out with https://pep8ci.herokuapp.com/.
There were no warnings left in the production code apart from the ‘line too long’ errors on the boilerplate Django code in ‘settings.py’.
JavaScript Linting
All JavaScript files were tested with JSHint. There were no warnings left in the production code apart from the ones detailed below -
script.js:
- The ‘google’ and ‘Places’ variables are defined in a separate script in the HTML template.
list-view.js:
- The ’USER_SORT_SELECTION’ and ‘google’ variables are defined in a separate script in the HTML template.
place-detail.js:
- The ’GOOGLE_PLACE_ID’ and ‘google’ variables are defined in a separate script in the HTML template.
place-add-form.js:
- The ‘google’ variable are defined in a separate script in the HTML template.
JavaScript Chrome Dev Tools Console:
All pages with JavaScript files were tested for errors with the Dev Tools console. Only one remains.
Warning on home page -
This code suggested didn’t work in our context, so was left as is.
A previous console warning - Loading the Google Maps JavaScript API without a callback is not supported
- for the JavaScript in ‘place-add.html’ was fixed with the help of this Stack Overflow post, which suggested the code: callback=Function.prototype
CSS Validation
The CSS file was validated with W3C CSS Validator. No errors are present in the production code.
HTML Validation
All pages were validated by the W3C Markup Validator. No errors are in the production code.
To overcome the errors that the Django tags would bring up, the pages were tested by selecting ‘View Page Source’ in Chrome and copying the rendered HTML into the validator.
Lighthouse Testing
All pages were tested with Google Chrome’s Lighthouse.
Home Page (desktop):
Home Page (mobile):
List Page (desktop):
List Page (mobile):
Detail Page (desktop):
Detail Page (mobile):
Add a Place Page (desktop):
Add a Place Page (mobile):
WebAIM WAVE Accessibility Testing
All pages were tested with the WAVE tool.
A single ‘redundant link’ error was left in the place-detail.html: if is a user is signed out, they are given a link to log in to leave a comment, which has a duplicate path to the ‘Log In’ link in the header.
Early testing with the WAVE tool flagged up insufficient contrast in some of the colours, which were then changed to meet the guidelines.
All user stories were tested to confirm that they meet their Acceptance Criteria. The following have all PASSED.
(View the EPICS, User Stories, Acceptance Criteria and Tasks on the GitHub Kanban Board).
EPIC: USER ACCOUNT
As a user I can sign up and log in to the site to leave comments and add places.
- Acceptance Criteria - PASSED:
- Users can create an account with a Sign Up page
- Users can log in with a Log In page
- Users can log out with a Log Out Page
EPIC: NAVIGATION
As a user I can view all places on a map to easily see which places are nearby.
- Acceptance Criteria - PASSED:
- Map page
- Saved places to show up as pins on the map
- Saved places to have photo and title in map popup info box
As a user I can view a list of the places to see titles, pictures and how many comments have been left.
- Acceptance Criteria - PASSED:
- List view page
- Photo is displayed
- Title is displayed
- Comment count is displayed
As a user I can sort the list of places by date, to see which places have been recently created.
- Acceptance Criteria - PASSED:
- User can sort place list by date (newest first)
- Sort-by-date is the default option
As a user I can sort the list of places alphabetically, to easily find a place by its name.
- Acceptance Criteria - PASSED:
- User can sort the place list alphabetically (A-Z)
EPIC: COMMENTS
As a user I can leave a comment on a place, to share my experience with other users.
- Acceptance Criteria - PASSED:
- Once signed in, a user can leave a comment on a place.
- User receives successful feedback message
- Comment form is in the detail view
As a user I can read my and other people's comments, to be inspired to visit a place.
- Acceptance Criteria - PASSED:
- Comments are displayed in the detail page
- Comment form submission redirects to detail page, so it can be read
As a user I can edit my comments, to correct or add information.
- Acceptance Criteria - PASSED:
- User can edit their own comments (not others)
As a user I can delete my comments, to allow me to change my mind.
- Acceptance Criteria - PASSED:
- User can delete ONLY their own comments
EPIC: FAVOURITES
As a user I can favourite and unfavourite a place, to keep a record of places to visit in the future.
- Acceptance Criteria - PASSED:
- User can click an icon to toggle the 'favourite' status of a place.
- 'Favourite' status is shown in the icon display.
As a user I can view a list of my favourite places, to easily see which places I plan to visit.
- Acceptance Criteria - PASSED:
- User can select 'Favourites' in the list page to view all of their favourites
EPIC: ADD A PLACE
As a user I can easily add a place by typing its name, to share my recommendation with other users.
- Acceptance Criteria - PASSED:
- Search bar with autocomplete
- Selected place is shown with name, address and photo
- User has the option to Save or Cancel
EPIC: ADMIN
As a site developer I can view a list of all data: places, comments and favourites, so I can moderate content.
- Acceptance Criteria - PASSED:
- Django admin panel with Places, Comments and Favourites
As a developer I can create, read, update and delete (CRUD) places, to create the initial content for the site, and moderate the Places added by users.
- Acceptance Criteria - PASSED:
- Places can be updated & deleted in Django Admin panel
EPIC: TESTING
As a developer I can create a suite of tests, to easily be able to find out if site updates create issues in the codebase.
- Acceptance Criteria - PASSED:
- Django unit tests with 90%+ coverage
EPIC: DEPLOYMENT
As a developer I can deploy to a hosting service, so that the site is available to the public.
- Acceptance Criteria - PASSED:
- Functioning deployment to Heroku
The following devices and browsers were used for manual & responsive UI testing.
- iPhone SE (2020)
- Safari (v16)
- Chrome (v114)
- iPad (6th Generation)
- Chrome (v111)
- Safari (v15)
- Mac Pro (Mid 2012)
- Chrome (v116)
- Firefox (v115)
- Dell Chromebook 3120
- Chrome (v103)
Bug | Fix |
---|---|
‘Add a Place’ error bug: if a user tried to add a duplicate place they would receive an error in the form and an error modal. | Using the CSS shown below to hide any form validation messages, and just have the main modal message. |
/* To hide the form error list (validation is done in the View) */
#place-add-form ul {
display: none;
}
Bug |
---|
‘Add a Place’ page: even though the input fields are hidden from user, they could potentially open developer tools and manually modify the form entries, leading to incorrect data. being submitted. |
The comments are displayed at the moment without showing any paragraph breaks that can be seen inside the Django admin panel. |
By forking the GitHub Repository we make a copy of the original repository on our GitHub account to view and/or make changes without affecting the original repository by using the following steps...
- Log in to GitHub and locate the GitHub Repository
- At the top right of the Repository, just below the GitHub navbar, click on the "Fork" Button.
- You should now have a copy of the original repository in your GitHub account.
- Log in to GitHub and locate the GitHub Repository
- Above the list of files, click "Code".
- To clone the repository using HTTPS, under "Clone with HTTPS", copy the link.
- Open Git Bash
- Change the current working directory to the location where you want the cloned directory to be made.
- Type
git clone
, and then paste the URL you copied in Step 3.
$ git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY
- Press Enter. Your local clone will be created.
$ git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY
> Cloning into `CI-Clone`...
> remote: Counting objects: 10, done.
> remote: Compressing objects: 100% (8/8), done.
> remove: Total 10 (delta 1), reused 10 (delta 1)
> Unpacking objects: 100% (10/10), done.
Click Here to retrieve pictures for some of the buttons and more detailed explanations of the above process.
- For changes you've made to reflect on the live site*:
- Type
git add <files changed>
- Type
git commit -m <description of change>
- Type
git push
- In Heroku, after pushing to Github - if 'automatic deploys' aren't enabled, manually deploy by clicking 'Deploy Branch' in the Manual Deploy section.
- Type
Follow the steps outlined in the Google documentation, to:
- Create a Google Cloud account
- Create a project
- Get a Google Maps API key
- Enable the Maps API and Places API
- Create a Cloudinary account, to host the static files.
- Copy your ‘API Environment variable’.
- Create an ElephantSQL account.
- Create a new instance.
- Copy the database URL.
You will also need to add the database to your Django settings.py file:
DATABASES = {
'default': dj_database_url.parse(os.environ.get("DATABASE_URL"))
}
- Create a Heroku account.
- In the dashboard, click on ‘Create new app’ from the ‘New’ dropdown menu in the top right.
- Name the app and choose a region.
- In the ‘Settings’ tab, click on 'Reveal Config Vars’.
- Enter the details for these Variables [you will also need these variables in your ‘env.py’ file for local use]:
- CLOUDINARY_URL
- DATABASE_URL (from ElephantSQL)
- GOOGLE_MAPS_API_KEY
- SECRET_KEY (from Django)
- In the 'Buildpacks' section click 'Add buildpack'.
- Select ‘Python’, and click 'save changes'.
- In the 'Deploy' tab, select GitHub as the deployment method, and click 'Connect to GitHub'.
- In the 'App Connected to GitHub' section, search for the GitHub repository name, select it then click 'connect'.
- Finally, either click ‘Enable Automatic Deploys’, or ‘Deploy Branch’ in the ‘Manual deploy’ section.
The original idea for this site was inspired by a mumsnet thread asking for recommendations of things to do in London. Some of the demo comments were rephrased from this thread.
All photos come from the Google Places API.
The following docs and tutorials were consulted.
Django
General Django tutorials:
Django ‘favouriting’ functionality:
Django messages:
Getting data from a Django model into javascript:
Google Maps API/JavaScript
(Official Google)
- Adding a map
- Adding markers
- Places Autocomplete
- Retrieving response data
- Adding multiple markers
- Places photos
(3rd Party)
- W3 Schools - Google Maps Intro
- WittCode - Google Maps JavaScript tutorial
- Stack Overflow - close info window when user clicks anywhere on the map
- My mentor Brian Macharia for his invaluable guidance.