Links is a bookmarking app built using Pythons Django framework, and hosted with Heroku. The main function of the app is to allow users to save the URL's of any website for easy reference. Users can also edit and delete their saved bookmarks, and there are numerous options to allow users to organise and display their collection in a way of their own choosing.
The deployed app can be found at links-sw.herokuapp.com
- 03.06.20 - New User Experience
-
- For testing, refer to the testing document.
The app can be split into 3 distinct sections
- The 'introductory' pages. These introduce potential users to the app, conveying information such as features, pricing, FAQ's, and providing login / register options.
- The settings pages. Here users can update their profile, get support, and upgrade to a Premium account.
- The main app page. This is where users can view, add, edit, organise, and delete their bookmarks.
Section 1 is only available to users who are logged out and unregistered, whilst sections 2 and 3 are only for logged in / registered users. This is intentional, as once a user has committed to creating an account, they should have no need for the initial pages, whose only purpose was to sell the app and encourage registration.
For this reason, the design of the introductory section is intentionally different to the other sections. Whilst they share similar design elements (colors, fonts, logos, etc) the intro pages make use of a single navigation bar at the top, whilst the main app and settings sections use a sidebar for navigation within the current section, and a top navigation bar for other features, such as search and swapping between sections (settings / main app).
- Save a webpage URL so I can revisit the page at a later date
- Store multiple 'bookmarks' within my account
- Organise my bookmarks into collections of similar bookmarks
- Have multiple pages, each containing multiple collections of bookmarks
- Order the bookmarks, pages, and collections in a way of my own choosing
- Edit my existing bookmarks should they need updating
- Move bookmarks from 1 collection and / or page, to another
- Delete bookmarks I no longer require
- Easily import bookmarks from the page I want to save with just a few clicks, without the need for multiple copy/paste actions
- Let the app scrape data from the page I want to save, and automatically create a suitable title and description
- Save an icon with the bookmark so I can easily identify the bookmark at a glance. Additionally, I should be able to choose the icon by either uploading my own, letting the app use the web page icon, or allow the app to create one of its own
- Update my personal details (email / password) if required
- Get support in the event of my being stuck, or if I have questions relating to the app
- Pay to upgrade to a 'Premium' membership tier, giving me access to features otherwise unavailable to users who are using the app for free.
- Have access to this app and my bookmarks across a range of devices and displays
- Manage the site and its users through a dedicated backend admin portal
- Be notified of support requests and be able to respond effectively
- Be able to track users who have signed up for a Premium membership
- Easily receive feedback from users
This app uses the Django framework
Django uses SQL databases. During development, the Django default sqlite3 database was used. When deployed to Heroku, a PostgreSQL database was used.
- Bookmark
- [collection, position]
- Collection
- [name, user, page]
- [position, name, page, user]
- Page
- [position, user]
- [name, user]
The app uses the Bootstrap 4 framework
Only 2 fonts are used throughout the app.
- Delius Unicase is used for buttons and headings h1 through h4. A fun comic style font, without being over the top. Great for buttons and headings, it stands out well on the page without being too much.
- Muli is used for all other text, including h5, h6 and paragraphs. Weight is set to 300, which gives the page a crisp and clean look.
Font Awesome 4 has been used throughout the project, across all sections, ranging from icons in the nav bar through to tooltips and everything in between.
Near the end of the project the app was upgraded to Font Awesome 5 but there were numerous display issues with the icons. Some were missing, many were sized incorrectly, and given there were no issues with the previous version, the app reverted back to v4, specifically version 4.7.0.
Wireframes were created using Balsamiq during the initial design phase of this project.
The first pages a user encounters are the 'sales pitch' pages. These 3 pages (index/home, pricing, FAQ's) are designed with a single purpose in mind - to convince the user to create a free account and give the app a try. Each page fulfils the following role as part of the overall pitch
Introduces the app and inside of a few short lines gives a high-level overview of what it does. A 4x4 grid of icons from familiar and well-known websites (chosen from a random pool of 100+) is displayed alongside to allow the user to make a connection between this app and (potentially) some of their favourite sites.
The features section is designed to give users a deeper insight into how the app functions, and what they can expect if they sign up. It focuses on customisation options, and lets users know they have a large degree of control over how they choose to use the app. Animated gifs are provided so users can see first-hand how the app works. For users wanting more information on features, there is a link to take them straight to a comparison table on the pricing page.
The next section introduces the Chrome browser extension, and the text focuses on how the extension makes it easier for users to use the app, and how it is free to use. There is a link to take the user to the Chrome Web Store so they can install it quickly and easily.
A user reviews section contains quotes from current users. Each review has been tailored to highlight and promote a specific aspect of the app.
Finally, at the bottom of the page, just before the footer, there is a link to take the user to the login / register screen. This is here as part of the overall effort to make it as easy as possible to create an account from any part of the site.
This page focuses on the free aspect of the app and assures users the app is fully functioning whether they choose to upgrade to Premium or not.
A comparison table reinforces this by showing the main differences between the 2 tiers. It is laid out in such a way so as to show that Premium membership just means 'bigger, better, more' rather than Free meaning 'limited functionality'.
A 'launch offer' is in place to let users know if they do decide to go for the Premium tier, it is only a 1 off payment for full lifetime access. The limited nature of this offer is designed to encourage users to sign up sooner rather than later.
As with the home page, the final section is a link to the user register / login screens.
A standard FAQ section, with questions picked that best promote a key aspect of the overall user experience, such as support, feedback, and so on.
Below this is a contact form that non-registered users can use to get in touch with the app's admin. Registered users can use it too, but as there is a dedicated support system in place inside of the app itself, it's expected the vast majority of contacts will be from non-users.
Lastly, as with previous pages, there is a button directing users to register / login.
Users can create a new account from this page. There are no restrictions on who can create an account, and it is free to do for all users. Users must provide a unique username and email address, which are checked against existing entries. A password is required, which must be entered twice to check it has been inputted correctly.
Tooltips are next to the username and password fields and provide guidance on how these fields should be completed.
In the event of these fields not being filled out correctly, or duplicate data being matched, the app will let the user know what the error is so it can be corrected.
Client-side validation has been removed for this form (and all others throughout the app). Form validation is all handled by the app itself. This is to provide consistency for the user across all forms.
Just underneath the 'Create Account' button, there is a link to the login page, should the user find they are trying to login instead.
The login page is identical to the register account page in most ways. The key differences (aside from the end result) are
- The login page only requires a username and password to proceed.
- There is a link to the password reset section for users who have forgotten / lost their password
The app uses the built-in password reset system provided by Django, although the pages have been customised to match the design of the rest of the site.
From the login page, a user can access the 'forgot your password' link whereby they provide an email address and if a match is found, they will receive an email with a link allowing them to create a new password.
The standard Django admin pages have been customised to make managing the app easier for admins. The look and feel has not been changed as it's not an area a standard user will ever access or even see, but additional layouts, filters, and actions have been added to aid the overall admin experience.
Some examples of admin backend customisation
'Groups' has been added to the list of filters, so the user can filter by All / Premium / No Group (Free Tier).
Additionally, a 'Membership Status' column has been added to the user overview page. There is no membership status field in this model, this is handled by Django User Groups, so this had to be defined in the admin.py file for the accounts app.
The individual bookmark view has been updated to group the different fields into relevant sections using fieldsets. 'added' and 'updated' have been added here too. They would not show up by default due to being read-only fields but have been added to assist with any potential troubleshooting that might be required when dealing with support tickets.
The overview page for these models have been adjusted to just display the key columns an admin might need when troubleshooting, and to avoid information overload. All field data is viewable when drilling down to individual collections and pages.
Any time a user completes the 'Get In Touch' form, the form data is stored in the app, rather than emailing admin users. This allows anyone who would deal with these messages to co-ordinate action / responses together, rather than multiple people potentially dealing with the same issue at the same time.
As well as customising the overview columns, a filter for the 'actioned' field has been added to allow an admin to view contacts by All / Yes / No.
Fieldsets are used in the detail page to clearly separate the content of the message from data an admin would need when processing items, such as date created and actioned.
The detail screen for individual purchases separates payment data (amount, date, id) from the user data such as name and email.
2 custom actions have been created, 'open selected tickets' & 'close selected tickets' to go alongside the default 'delete....' action.
Overview columns show just top-level information and a custom column called 'Admin Comments' has been added. This checks to see if the field 'Admin Comments' contains any data and displays accordingly, allowing admins to see at glance if work has already started on an individual ticket.
A filter has been added for the status field to allow an admin to filter by All / Open / Closed.
The individual ticket display has been customised to separate the ticket data into sections to make it easier for admins to quickly focus on the data they need. There are 3 areas, split into user details, ticket details, and admin actions (comments and status).
The app makes use of the built-in messages framework that comes with Django. Any time the app needs to communicate an important event to the user, such as confirmation of an action's success or failure, the message is flashed at top of the screen, just below the top navigation bar.
The message tag is used to set the bootstrap color class (success green, orange warning, and so on) so the user can immediately tell what type of message it is. Messages can be manually closed, or they will disappear on a new page load / refresh.
Registered users can open a support ticket to help with anything related to the app. The form is simple, requiring only a title and message, and the ticket will be opened and viewable from within the admin panel. Details of how an admin would manage this ticket can be found in the previous section on the admin backend, specifically this section on tickets.
From a user perspective, once the form is submitted, they will receive an email confirming receipt of their ticket which will also contain a copy of their support request.
Users can upgrade to the Premium tier from the Premium page in the Settings section. The page is designed to first show visitors what Premium gives them over the free tier and then follows it up with the limited lifetime offer to further incentivise the purchase.
When the user is ready to upgrade, a simple form is available for their payment details. Only limited information is collected, just their card details and basic identifying information (name and postcode).
This form connects to the Stripe API to process a user's card details. No card details are stored locally or on the server, they are only sent to Stripe and then discarded.
Payments (and Premium functionality) can be tested by upgrading to Premium using Stripes basic test card numbers, which are the following
Card Number - 4242 4242 4242 4242
Expiry - Any future date
CVC - Any 3 digits
On successful completion a PremiumPurchase
object is created to record the event and the user is added to the 'Premium' group. This group has no special permissions and is used only to differentiate between Free & Premium users.
Premium users can do things that users on the free tier cannot, such as access telephone support, remove ads, and have more bookmarks, collections, and pages.
There are 2 functions used to check if a user is a Premium member, and the function used depends on which part of the app needs to know, i.e. the back-end logic, or the front-end templating.
Updates the context dictionary with a key value pair of is_premium: (Boolean)
and is used the by templating language when deciding which content to display
Returns a Boolean. When a user tries to perform an action that has the potential to be limited by them being on the Free tier, for example adding a Page, this function checks if this action would cause the user to exceed the limits imposed by the Free tier.
If the check passes, nothing happens and the app works as normal, leaving the user oblivious to the fact a check took place.
If the check fails, the user is informed of this, advised this is due to them being on the Free tier, and is then redirected to the Premium page to encourage them to convert to Premium.
The user profile page allows the user to update their email address and password, using 2 separate forms. It is as straight-forward as entering the required information and pressing submit. Providing the form passes the validation checks, the update is immediate.
This page also contains a user preferences
section. Currently this only consists of one entry - an option to set / reset the display warnings when there are too many columns - but this can be expanded as and when more functionality is added.
The final section provides basic data on how many bookmarks, collections, and pages the user has currently. This can eventually be expanded into its own section for Detailed User Statistics
When logged in, the top navigation bar allows the user to move between different sections of the app (bookmarks, options, log out) and also search their entire collection.
The element is fully responsive and adjusts to the current screen width. On widths < 768px the sidebar is hidden from view so the app adds the burger icon / 3 horizontal lines to the left to show the user it can be expanded back out.
Additionally, the + Add Bookmark
from the sidebar button is no longer visible so the top navigation bar adds a +
icon to allow the user to still easily add a bookmark without having to manually open the sidebar.
On widths < 576px the search bar is replaced by a search icon, again to save space, and when clicked it expands into a search box allowing the user to type in their search query.
The side navigation bar takes 2 forms depending on whether the user is within the bookmarks or settings section.
Whilst managing bookmarks, the sidebar will contain a list of the pages.
There is a lot of functionality built into this section, allowing a user to:
- Navigate to a different page
- Add a new page
- Reorder existing pages
- Edit the current pages' settings
- Rearrange the collections on the current page
- Change the number of columns displayed on the current page
- Rename the current page
A simple list allowing the user to navigate between the different settings pages.
Regardless of section, the top of the sidebar will always contain the app logo and + Add Bookmark
button
Using the search bar at the top of the page, a user can search their entire collection by title, and view all the results in one place.
The individual bookmarks are displayed in full
mode (See Bookmark Display Options for more detail on this) and have the same actions available (Edit, Move & Delete) as when viewing from within their respective collections and pages.
Up to 10 results will be displayed per page, and in the event of there being more than 10 results, they will be spread over multiple pages for the user to browse through.
Results are displayed in date added
order, earliest first. At a later date, this would be fleshed out more to give the user more control over the search results, in the form of configurable sort orders and more pagination options.
If a user searches with a blank search term, the app will return all bookmarks.
Adding a bookmark is as straightforward as filling out a form and clicking the Add Bookmark
button.
The user has complete control over this and can enter the information manually or click Autofill
and let the app try and populate the fields itself. The same applies for the bookmark icon; the user can upload their own, scrape the web page, or let the app generate its own
The user should also tell the app where to store the bookmark - the page and collection. The default page is the currently selected page, and the default collection is the first collection on this page, unless the user has chosen to add a bookmark using the +
icon in the collection header. If they have, the default collection destination will be the collection from which the +
icon was clicked. If a user comes from, or chooses, a page that currently has no collections, the destination collection drop-down will reflect this by being empty. If the user tries to submit to an empty collection an error message will tell them to choose a page with at least 1 collection
When adding a bookmark, just underneath the URL
field is a URL status
checker. The purpose of this is to provide real-time feedback on if the URL being entered is valid or not. Using ajax the app will send the URL to the check_valid_url
function and using the Python requests library it will return a Boolean result.
To keep requests at a reasonable level, the function is called only after 1 second has elapsed since the last key release within the URL field.
A user can choose to allow the app to scrape the address in the URL
field by clicking the Autofill
button and have the title
and description
field filled in automatically. Where no data can be scraped, the text fields will be filled with a message stating this.
Additionally, Autofill
will also attempt to get the sites icon and if successful this will show in the Icon Preview
area.
After Autofill
has run, a message just below the button will be displayed to let the user know if it has been successful. Success is determined by the app being able to connect to the site and attempt to scrape, not by what it returns.
If a user doesn't include the http://
or https://
at the start of the URL and just types, for example google.com
, then before scraping, the app will prepend the URL with https://
so the app scrapes https://google.com
.
The URL is prepended with https://
over the less secure http://
as the vast majority of sites now use https://
and it's right that if the user doesn't specify, the app should default to the more secure of the two options.
When it comes to storing an icon to go alongside the bookmark, users have 3 options
- Scrape the site with
Autofill
and use whatever image is returned - Upload their own
- Let the app create one
Each time one of the above operations is successful, a preview of the result will be displayed in the icon preview
section. No further action is required by the user to select this image - the contents of this preview will be saved with the bookmark when the form is submitted.
If a user chooses to let the app create a bookmark for them, nothing is actually created, and the app doesn't store anything in the icon field. Instead, every time the app needs to display a bookmark icon, if it finds an empty field, it then generates the bookmark there and then.
The generated icon is the capitalised first letter in the bookmark title on a colored tile. This is taken care of by the filters contained with icon_styling.py
. The filters set the size of the icon and text, based on the chosen display option. The tile color is decided by the position of the letter in the alphabet. This way, tile colors and letters are consistent across the entire app.
Every bookmark has an icon to its right that when clicked, provides a number of bookmark specific options.
The Edit Bookmark
page is similar to the Add Bookmark
page in both style and functionality. The only differences are
- The form is already filled in with data for that particular bookmark
- The
Destination Page
&Destination Collection
drop-downs are not shown as the Bookmark isn't moving anywhere.
The Move Bookmark
page is almost the opposite of the Edit Bookmark
page. The only fields are drop-downs for Destination Page
& Destination Collection
so the user can choose where to move the bookmark to.
The Bookmark itself is displayed so the user can see at a glance which bookmark is being moved, but there is no option to edit the details.
When a user selects the Delete Bookmark
option, a modal appears asking for confirmation. The user can either choose to delete the Bookmark or cancel the request. All deletions are permanent.
On any given page, a user has the option to add a new Collection. If the page is currently empty, there is just 1 large box directing the user to create a collection.
This is placed prominently as the user can do little else until a page has at least one collection. If a page contains 1 or more collections, the option to add another is still present, but is located a little more discretely and is positioned after the last collection in each column.
Whilst not part of the project submission, a Chrome extension was created to improve the overall UX when adding bookmarks. The repo can be found here and the extension itself is hosted on the Chrome Web Store.
Adding bookmarks is easy enough when just copy / pasting a URL into the URL field of the Add Bookmark
page but I realised early into the project it just was not convenient. Users are not expected to spend a lot of time in the app itself, rather just use it for reference when looking for a particular site or page. It ended up being a real chore using the app if every time you want to save a page for later you had to open a new tab, load the app, select Add Bookmark
and so on.
This extension streamlines the process massively. Now, users only need to click the extensions icon and it will open the app in a new tab, with the page they were on now in the URL field and the app will also automatically start a scrape to give the user some data to get started with. In many cases, this will be enough and allows a user to save a bookmark and get back to where they were in 3 steps:
1. Click on the extension icon
2. Click 'Add Bookmark'
3. Close the tab once the 'import successful' message is displayed
The Import URL
page is identical to the Add Bookmark
page in almost every way, the only differences being:
- The name
- No sidebar or top navigation as the tab is designed to be closed as soon as the import is complete
There is currently 1 unresolved bug with the extension. When the Chrome browser is opened for the first time, the first click of the extension does nothing. All subsequent clicks work as expected. If another new window is opened, the click works first time.
Since discovering this, I haven't yet investigated. It might be an easy fix. It might not. It is likely somewhere in between but it's not a priority as it doesn't affect the app so for now it's on hold.
Bookmarks are grouped together inside of their own containers, which are called collections
. These collections can be moved around the page as the user sees fit.
Users can access this functionality from the Arrange Collections
button, located in the sidebar by clicking the cog icon next to the current page name.
Collections are arranged using a drag & drop system and the number of columns being arranged is dependent on the current column setting. The app ensures that when a user rearranges a collection, that arrangement carries over to all column variations. So, if a user makes a change whilst using 5 columns on screen at once, but then decides to switch to a 3 column view, those changes will be represented when switching. It's not perfect, but it's close, and definitely better than forcing the user to have to rearrange their collections manually every time they adjust the number of columns.
The user can choose from 3 different display styles, on a per collection basis.
On Full
all 3 information fields are displayed: URL
, Title
& Description
. More often than not, the description contains more text than can be comfortably displayed, so if a user hovers over this area, a tooltip will display showing the full text.
These tooltips only display on devices with a pointer. I considered adding another icon to allow users to click and view the tooltip but felt this added too much clutter to the UI.
Normal
removes the Description
(and therefore the tooltip), but still keeps the Title
& URL
.
Minimal
displays the Title
only. Users can still see at a glance exactly what this bookmark is, with the advantage of its small size making it ideal for small screens and collections with a lot of bookmarks.
Regardless of how the user chooses to display their bookmarks, the bookmark and options icons are always visible.
The user can choose to sort and order their bookmarks in a number of different ways, on a per collection basis.
These are Name
, Date Added
and Date Updated
and the user can choose to sort these by ascending or descending. When sorting in one of these modes, manual sorting is disabled. The Manual Sort
button at the top of the collection is greyed out and disabled. Additionally, a tooltip is added to let the user know they need to re-enable manual sort to use this feature.
If a user prefers to sort their bookmarks in a particular way (for example, most used first) they can do this. Once the Manual Sort
button is clicked, the bookmark style changes to show the mode is active.
Users can then order the bookmarks as they please using drag & drop, and turn it off once complete. On smaller displays, the width of the bookmark in sort mode is reduced to leave a gap to the right so users can scroll up and down the screen without accidently grabbing a bookmark. This is especially useful for large collections.
A banner advert is displayed at the bottom of most screens for non-Premium users.
Its position has been placed in such a way as to be non-intrusive, but still noticeable enough to generate clicks and to also make the user want to get rid of it via the Premium upgrade.
The only screens that do not display these adverts are screens where no user is logged in (about, pricing, FAQ, user auth) and the About
page from within the settings section.
Quite soon into development it became apparent this type of app was definitely more suited to desktops and generally the wider the display, the better the experience. This was an issue as the app needed to function well on displays of all sizes, but I didn't want to compromise the UX on medium and large displays, just to make it serviceable on smaller screens.
The main issue was that of multiple columns on small width displays. Taking mobiles as an example, anything more than 1 column looked a mess and, in some cases rendered the app useless. It still worked, but from a user's perspective, it was more trouble than it was worth.
The final solution manages to keep the UX consistent across all screens and works as follows.
For each display width, there is a maximum number of columns at which the app displays well, and choosing to use more columns than this recommendation would impact the overall UX, usually by making elements unreadable due to being squashed on screen.
Min Display Width | Max Display Width | Max Columns |
---|---|---|
0px | 575px | 1 |
576px | 991px | 2 |
992px | 1199px | 3 |
1200px | Any | 5 |
If the app detects a width and column combination outside of these recommended limits, a warning is displayed, advising the current settings may not provide the best experience.
The user can then choose to either change to the recommended number of columns or dismiss the warning.
If the user opts to change the number of columns, it is assumed the user wants to continue using the app within the recommended limits, and therefore any future width adjustments will trigger a similar warning.
However, one of the key goals of this app has always been to give the user a choice in how they use it. If they want to display more columns than recommended, they can. If they click to dismiss this warning, all future warnings will also be suppressed.
If the user changes their mind and decides they want the warnings back, this can be adjusted from the settings > profile > user preferences
section of the site.
Additionally, this preference is stored using local storage so if a user chooses to dismiss warnings on desktop, but then accesses the app via their mobile device, they will still continue to receive display warnings, until dismissed.
Another, lesser issue, was that of the sidebar taking up too much space on smaller widths. This was fixed early into development by having the sidebar hide when below a certain width and making it toggleable instead.
Even though the app already contains a lot of functionality, there are still things I would like to add at a later date. The list is huge, but these are the 3 I'd prioritise first.
Once a solid user base had been built, with a not insignificant number of Premium members, I'd switch the payment model to a monthly / annual subscription format instead of the current one-off lifetime membership system.
Everyone loves a bar chart, right? The profile page can be expanded so instead of just totalling the user's bookmarks, pages & collections, there could be more detailed stats at the user level and site wide statistics too.
This was actually planned from the beginning and whilst it would have been relatively simple to add, time just ran out. Users would be able to mark individual collections and / or pages as public and each user would have their own public link, along the lines of /users/[username]
. Users could share this URL with others, including non-users, and make their bookmark collections publicly available.
- HTML used as the markup language
- CSS used to style the HTML
- JavaScript used mostly for DOM manipulation
- Python3 used to run the backend application
- Bootstrap 4 provided the CSS framework
- Font Awesome 4 is used for the various icons throughout the app
- Google Fonts serves the fonts used in the app
- jQuery is used for many things, for example DOM manipulation, buttons, AJAX requests, and more
- Swipe.js is a small jQuery plugin that detects swipes and is used to hide the sidebar on mobile devices.
- jQuery UI is used to allow the user to drag and drop elements on the page when sorting bookmarks, collections, and pages
- jQueryUI Touch Punch is a jQueryUI hack that makes jQueryUI play nice with touch events
- Tippy.js is used to provide tooltips throughout the app
- Django v2.2 is the Python framework used to power the app itself. Various Python packages have been used and they are listed below.
Python Libraries - click to view
Packages that are only dependencies of others are not included. The full list can be found in the requirements folder.
Package | Version | Description |
---|---|---|
beautifulsoup4 | 4.8.2 | A library that makes it easy to scrape information from web pages |
boto3 | 1.11.6 | The AWS SDK for Python, used to connect to S3 buckets |
coverage | 4.5.4 | Package for measuring code coverage, typically during test execution |
dj-database-url | 0.5.0 | Utilizes the 12factor inspired DATABASE_URL environment variable to configure a Django application. |
Django | 2.2.7 | The Django framework itself |
django-appconf | 1.0.3 | A helper class for handling configuration defaults of packaged Django apps gracefully |
django-cleanup | 4.0.0 | Automatically deletes files for FileField , ImageField and subclasses |
django-crispy-forms | 1.8.1 | Controls the rendering behaviour of Django forms |
django-storages | 1.8 | Provides a variety of storage backends in a single library, used in this case to connect with AWS S3 |
favicon | 0.7.0 | Library that returns URL's for website favicon(s) |
gunicorn | 20.0.4 | A Python WSGI HTTP Server for UNIX |
Pillow | 7.0.0 | Python Imaging Library (Fork) for handling bookmark icons |
psycopg2 | 2.8.4 | PostgreSQL database adapter |
requests | 2.22.0 | Simplifies HTTP requests |
stripe | 2.40.0 | Library for Stripe’s API |
whitenoise | 5.0.1 | Simplified static file serving for WSGI applications |
- Visual Studio Code - The IDE used during development
- AWS S3 Buckets - Used to host and serve user uploaded / scraped images
- Stripe - For accepting payments when users upgrade to Premium
- Photoshop - Image editing and manipulation
- TinyPNG - To reduce image file sizes
- Balsamiq - For creating the initial wireframes
- ScreenToGif - For recording the animated gifs on the features page
- StackEdit - Markdown editor used for the readme and testing documents
- Imgur - Hosting external image files used with this documentation
- Git - For version control
- GitHub - Remote storage and sharing of the apps code
- Spotify - Because music
- SQLite3 - The default Django database, used during development
- PostgreSQL - The production database, provided by, and deployed to, Heroku
- Heroku - Used to host the deployed version of this app
For testing, refer to the testing document.
The following instructions are based on the user running VSCode on Windows 10. If your IDE / OS is different, your commands may differ slightly, but the process remains the same.
You will need Python 3 (ideally the latest version, but 3.5 as a minimum) installed on your machine. You will also need PIP which comes preinstalled with Python versions 3.4 and later. Having Git is also highly recommended.
The following steps will allow you to deploy locally:
- Save a copy of the repo on your local machine or use
git clone https://github.com/steview-d/bookmarks.git
and cd into the correct folder using the terminal. - Create a virtual environment, using
python -m venv .venv
where.venv
is the environment name. - Activate the virtual environment with
.venv\Scripts\activate
- Install any required modules with
pip install -r requirements.txt
- Create a file called
env.py
in the project root and set up the required local environment variables, like below
import os
os.environ.setdefault("SECRET_KEY", "<Enter Your Django Secret Key here")
os.environ.setdefault("HOST_NAME", "127.0.0.1")
os.environ.setdefault("DEBUG", "1")
# email
os.environ.setdefault("EMAIL_ADDRESS", "<Email Address App Will Use To Send Emails")
os.environ.setdefault("EMAIL_PASSWORD", "<Password For Above Email Address>")
os.environ.setdefault("EMAIL_HOST", "<Your outgoing mail server")
os.environ.setdefault("EMAIL_PORT", "<Your smtp port>")
# stripe keys
os.environ.setdefault("STRIPE_PUBLISHABLE", "<Enter Your STRIPE_PUBLISHABLE value here>")
os.environ.setdefault("STRIPE_SECRET", "<Enter Your STRIPE_SECRET value here>")
NOTE: When deploying locally, there is no need to set up AWS S3 buckets as all media files are served locally
- Run
python manage.py runserver
in the terminal. This will create a local sqlite3.db file for us to use. - Once this has run, close the server with
CTRL + C
- In the terminal, run
python manage.py migrate
to set up the database. There is no need tomake migrations
as the migration files are already present. - Create a superuser using
python manage.py createsuperuser
- you will need to enter a username, email address and password (twice). - Start the server up again with
python manage.py runserver
- Open the app in a browser window by navigating to
http://127.0.0.1:8000/accounts/login
and login with your newly created superuser. - Once logged in, navigate to the admin panel at
http://127.0.0.1:8000/admin
and click ongroups
. - Click
Add Group
(top right corner) and in the first field (Name
) enterPremium
(case-sensitive), and pressSave
. - You will likely want your user to have access to Premium features, and you can do this in one of 2 ways
- Add them to the
Premium
group from the Admin panel by selectingUsers > 'Your User' > Groups > Premium
. Once Premium has been added to theChosen groups
column, just clickSave
. - Or sign up for Premium using the Premium upgrade form within
Settings > Premium
. You can use the Stripe test card details found here.
- Add them to the
Once these steps have been completed, the app will be up and running on your machine.
These instructions make the following assumptions
- The app has been deployed locally, following the above steps, and then pushed to your own GitHub account.
- The env.py should be added to your .gitignore
- You have created and configured an AWS S3 Bucket for serving the media files
- You have a Stripe account
There should be no reason to create a Procfile or requirements.txt file.
These files should already be present in the cloned repository.
Once all the above is in place, the instructions below will enable you to deploy to Heroku.
- Go to heroku.com and log in or create an account.
- Add a new app, give it a name, choose a region and click
Create app
. - On the dashboard, click the
resources
tab. From within theaddons
input field start typingpost
until you can selectHeroku Postgres
and select it. - In the plan box that pops up, select
Hobby Dev - Free
, then clickProvision
. - Once set up, click the Postgres database, select the
settings
tab andDatabase Credentials
heading. Make a note of theURI
value, you will need it later. It will start withpostgres://...
- Go back to the Dashboard, and from the
Setttings
tab, click theReveal Config Vars
button and add the following key / value pairsEMAIL_ADDRESS
- The email address you want the app to send emails fromEMAIL_PASSWORD
- The password for above email addressEMAIL_HOST
- The Outgoing Mail Server for your email, for examplesmtp.gmail.com
EMAIL_PORT
- Your SMTP port, usually25
,465
, or587
SECRET_KEY
- Use a Django Secret Key Gen, for example this one.HOST_NAME
- The URL you are deploying to, for examplelinks-sw.herokuapp.com
STRIPE_PUBLISHABLE
- Your Stripe APIPublishable key
STRIPE_SECRET
- Your Stripe APISecret key
DATABASE_URL
- This should already be here after you created the Postgres db, but if not, it is the Postgres URI you made a note of earlier.AWS_ACCESS_KEY
- Your AWS Access KeyAWS_SECRET_ACCESS_KEY
- Your AWS Secret Access KeyAWS_STORAGE_BUCKET_NAME
- The name of the Bucket being used for this app
- Prepare the new Postgres db, by following these steps
- From your local IDE, add the following entry to your env.py file. (The
postgres//....
value can be copied from the Heroku config vars) and restart your IDE to allow the new environment variable for the database to take effect.os.environ.setdefault( "DATABASE_URL", "postgres://<your value here>" )
- Your local deployment should now be connected to the remote Postgres db so you can run:
python manage.py migrate
to set up the databasepython manage.py createsuperuser
to set up your admin account.
- Delete / comment out the
DATABASE_URL
entry in yourenv.py
file.
- From your local IDE, add the following entry to your env.py file. (The
- Back in the Heroku Dashboard, click the
Deploy
tab and scroll down toDeployment Method
. SelectGitHub
and link your account and repository. - Scroll down further to
Manual Deploy
, choose the branch you wish to deploy and clickDeploy Branch
- Wait for the app to build, and once complete, click
view
to launch your app in the browser. - Log in with the superuser details you created and navigate to the admin panel at
your-deployment-url/admin
- Repeat the instructions from Local Deployment to add the
Premium
group and give access to your superuser:- Click on
Groups
thenAdd Group
(top right corner) and in the first field (Name
) enterPremium
(case-sensitive), and pressSave
. - You will likely want your superuser to have access to Premium features, and you can do this by adding it to the
Premium
group from the Admin panel by selectingUsers > 'Your User' > Groups > Premium
. Once Premium has been added to theChosen groups
column, just clickSave
.
- Click on
All content, words, and design are my own, unless otherwise stated below.
The original bookmark icon was sourced from favpng.com.
The site icons for this app (favicons, and so on) were generated by realfavicongenerator.net using a modified version of the original bookmark icon as the source.
The icons in the 4x4 grid on the index were all scraped from their own sites, using this app. The tiled background image used in areas of the app was put together using icons scraped by this app.
The portrait images for the user reviews
section were used with permission from Vecteezy
The FREE
.png image file used on the pricing page was sourced using Google image search.
Used to pull Django csrf tokens from the page for use with ajax requests. Code sourced from Emad Mokhtar @ stackoverflow
Spinner used when processing ajax requests sourced from https://tobiasahlin.com/spinkit/ and used with minor modifications.
All other code, outside of frameworks and libraries, is my own.
Vitor Freitas at https://simpleisbetterthancomplex.com - outside of Stack Overflow, this was usually the place that helped me solve some of the bigger problems I encountered. Some great articles that cover key concepts in an easy to understand way.
The Try Django 2.2 mini project is a free step-by-step guide to building a blog with Django, and even though Django blog tutorials are ten a penny, this one is highly recommended.
Thanks to Attila Szaloki & Precious Iijege for help with testing and feedback.
03.06.20
A change to what a new user sees when first logging in after registering an account. Previously, new users were greeted with a brief message at the top of screen informing them a page had been created for them, and to create a collection before trying to add a bookmark. Now, when a new user logs in for the first time, a modal window opens in the middle of the screen and shows this:
When the modal is closed, the user now has a collection with 5 bookmarks contained within, giving the user opportunity to better see how the app works, before they need to start creating their own bookmarks.