Developer Documentation

This site was built using the Django web framework (v2.0.8). Each section has been implemented within separate apps, each detailed in this documentation. The code is available on github. If you are unfamiliar with Django, you can find out more on the official doc site: docs.djangoproject.com


Apps

  • accounts (User account registration, sign in/out and profile)
  • cart (New features and feature upvote purchases)
  • checkout (Process payments using Stripe)
  • comic_strip (Build Customised comic strips)
  • forum (Define and comment on topics for discussion)
  • productivity (Graphs summarising tickets raised over time)
  • tickets (Raise bugs/features with upvotes/comments)

accounts

User sign in/out and registration and profile display. The login_required decorator is used across several apps to redirect signed out users to the login page with a subsequent redirection back to the page where the access attempt took place. This is based on a modified user model that uses email address + password authentication instead of user id and password.

Models: MyUserManager and User
Urls:
  • "login" - email address and password sign in
  • "logout"
  • "register" - full name, email address and new password

cart

The cart is for users to purchase feature upvotes and to request new features. Browser session variables are used to populate the cart before ultimately populating the backend database via the checkout app.

Urls:
  • "cart" - listing of all features/upvotes in the current session
  • "adjust-cart/< title of ticket >" - adjust the price of a new feature
  • "adjust-upvote-cart/<ticket id>" - adjust the amount on offer for an upvote

checkout

Gathers cart contents and generates a single Stripe transaction. Providing a successful payment, this stores the transaction in the backend (excluding payment details) and empties the cart.

Urls:
  • "checkout" - listing of all features/upvotes in the current session

comic_strip

Signed in users can add a new comic strip and add new frames to their own comic strips. Guests can view the overall comic strip listing and view individual comic strips.

Models: ComicStrip and ComicStripFrame
Urls:
  • "/" - home page - view a paginated listing of all comic strips
  • "add" - add a comic strip
  • "view/< comic strip id >" - view a comic strip
  • "add-frame/< comic strip id >" - add a comic strip frame

forum

A simplified forum allowing user based discussion on comic related topics. Guests can only view forum topics and comments.

Models: ForumTopic, ForumComment and ForumCommentReply
Urls:
  • "forum" - list of forum topics
  • "add-topic" - add a new forum topic
  • "view/< topic id >" - view all comments for a forum topic
  • "add-comment/< topic id >" - make a comment on a forum topic
  • "reply/< comment id >" - reply to a comment within a forum topic

tickets

Bugs can be raised by signed in users. Similarly, new features can be requested - these need to be paid for so this app ties in with the cart and checkout apps. Users marked as staff can edit tickets to update their status and enter the implemented/proposed solution or next steps.

Models: Ticket, TicketUpvoter and TicketComment
Urls:
  • "tickets" - paginated listing of all bugs and features, sorted by descending upvotes
  • "add" - add a comic strip
  • "edit/< ticket id >" - edit a ticket (staff only)
  • "view/< ticket id >" - view a ticket, user has access to upvote and comment from here. A paginated list of comments is available.
  • "upvote/< ticket id >" - upvote a ticket, only one upvote is allowed per user
  • "comment/< ticket id >" - comment on a ticket, no user limit on comments

Models

accounts

MyUserManager (BaseUserManager)

Re-definition of create_user and create_superuser functions to adjust for email based authentication instead of user name.

User (AbstractBaseUser)
              
    email          = models.EmailField(unique=True, null=True)
    first_name     = models.CharField(max_length=50, null=True, blank=True)
    last_name      = models.CharField(max_length=50, null=True, blank=True)
    is_staff       = models.BooleanField('staff status', default=False)
    is_active      = models.BooleanField('active', default=True)
    USERNAME_FIELD = 'email'
    objects        = MyUserManager()
              
            

checkout

Order
              
    full_name       = models.CharField(max_length=50, blank=False)
    phone_number    = models.CharField(max_length=20, blank=False)
    country         = models.CharField(max_length=40, blank=False)
    postcode        = models.CharField(max_length=20, blank=True)
    town_or_city    = models.CharField(max_length=40, blank=False)
    street_address1 = models.CharField(max_length=40, blank=False)
    street_address2 = models.CharField(max_length=40, blank=False)
    county          = models.CharField(max_length=40, blank=False)
    date            = models.DateField(auto_now_add=True)
              
            
OrderTransaction
              
    order  = models.ForeignKey(Order, null=False, on_delete=models.CASCADE)
    ticket = models.ForeignKey(Ticket, null=False, on_delete=models.CASCADE)
    cost   = models.DecimalField(max_digits=4, decimal_places=2, blank=False)
              
            

comic_strip

ComicStrip
              
    title       = models.CharField(max_length=100, blank=False)
    description = models.TextField()
    author      = models.ForeignKey(settings.AUTH_USER_MODEL,
                               related_name='contacts', on_delete=models.CASCADE)
            
ComicStripFrame
              
    comic_strip = models.ForeignKey(ComicStrip, on_delete=models.CASCADE)
    narrative   = models.TextField(max_length=300)
    image       = models.ImageField(upload_to='images')
    sequence    = models.IntegerField(null=False, blank=False)
    move        = models.IntegerField(null=True, blank=True)
              
            

forum

ForumTopic
              
    topic_title  = models.CharField(max_length=150)
    date_created = models.DateTimeField(auto_now_add=True)
              
            
ForumComment
              
    forum_topic  = models.ForeignKey(ForumTopic, on_delete=models.CASCADE)
    author       = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    comment      = models.TextField()
    date_created = models.DateTimeField(auto_now_add=True)
              
            
ForumCommentReply
              
    forum_comment = models.ForeignKey(ForumComment, on_delete=models.CASCADE)
    author        = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    comment       = models.TextField()
    date_created  = models.DateTimeField(auto_now_add=True)
              
            

tickets

Ticket
              
    requester        = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title            = models.CharField(max_length=50)
    description      = models.TextField()
    solution         = models.TextField('Solution / Proposed solution', null=True, blank=True)
    date_raised      = models.DateTimeField(auto_now_add=True)
    date_last_saved  = models.DateTimeField(auto_now=True)
    type             = models.CharField(max_length=10, choices=TICKET_TYPE_CHOICES, default='Bug')
    status           = models.CharField(max_length=12, choices=TICKET_STATUS_CHOICES, default='Logged')
    upvotes          = models.IntegerField(null=True, blank=True)
    feature_cost     = models.DecimalField(max_digits=4, decimal_places=2, null=True, blank=True)
              
            
TicketComment
              
    author       = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    ticket       = models.ForeignKey(Ticket, on_delete=models.CASCADE)
    date_comment = models.DateTimeField(auto_now_add=True)
    comment      = models.TextField()
              
            
TicketUpvoter
              
    upvoter_user   = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    upvoter_ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
              
            

Views

All of the views are documented by comments in the code, which should suffice for a good level of understanding of each view. Here is a brief overview of all views:

accounts

login, logout, register

Email/password authentication for signing in and registration using Djangos built in authentication module: django.contrib.auth

cart

view_cart, adjust_cart, adjust_upvote_cart

Listing of session based cart contents. There are two cart lists - one for new features and another for feature upvotes.

checkout

checkout

This generates a transaction based on cart contents and makes use of the Stripe API to charge the users credit card. For testing purposes you can use card number 4242424242424242. Assuming a successful payment is fed back from Stripe: save any new features as a ticket in the backend, similarly, any feature upvots need to be registered against the relevant ticket.

comic_strip

comic_strip_listing, comic_strip, comic_strip_add, comic_strip_frame_add

These views cover the end user product to generate custom comic strips: paginated listing of comic strips, ordered by title, view all frames within each comic strip (also paginated in the order added). Logged in users can also add comic strips and individual frames within a comic book. The user must be the owner of the strip to add new frames.

productivity

productivity

This view builds five cuts of the backend ticket data which is then used to portray work rate using a series of line charts and top N lists. The line charts show daily, weekly and monthly figures of tickets raised over time. These charts are built using JavaScript libraries chartist.js and moment.js

tickets

ticket_listing, ticket_view, ticket_add, ticket_edit, ticket_upvote, comment_add

For the ticket listing, bugs/features are order by descending upvotes so that the most popular tickets appear at the top. The ticket_add view performs differently for bugs and features. As bugs are free to add, they are simply added to the tickets and saved in the backend database. Features however must be paid for before they are considered. These are added to a shopping cart. Only upon checkout will a feature be committed to the backend and listed on the site.