Welcome to GardenHub’s documentation!

GardenHub is an open source web application that facilitates food distribution for community gardens. It’s written in Python using the Django web framework.

GardenHub’s source code may be found on GitHub.

Developer Guide

This section will explain how to hack on GardenHub.

Models

_images/db-schema.png

Querying objects

class gardenhub.managers.OrderQuerySet(model=None, query=None, using=None, hints=None)

Custom QuerySet for advanced filtering of orders.

The following fields enable filtering orders via Order.objects. For example,

from gardenhub.models import Order

# All open orders
orders = Order.objects.open()

# All open orders that haven't been picked today
orders = Order.objects.open().unpicked_today()
active()

Orders that are happening right now.

closed()

Orders that have finished or were canceled.

inactive()

All orders that aren’t happening right now.

open()

Orders that have not finished but also may not have begun.

picked_today()

Orders that have at least one Pick from today.

unpicked_today()

Orders that have no Picks from today.

upcoming()

Orders that have not yet begun but are scheduled to happen.

class gardenhub.managers.UserManager

Custom User manager because the custom User model only does auth by email.

get_or_invite_users(emails, request)

Gets or creates users from the list of emails. When a user is created they are sent an invitation email.

Full model reference

class gardenhub.models.Affiliation(*args, **kwargs)

A group of affiliated gardens.

exception DoesNotExist
exception MultipleObjectsReturned
class gardenhub.models.Crop(*args, **kwargs)

A crop represents an item that may be picked, such as a zuchini or an orange. Crops are stored in a master list, managed by the site admin, and may be listed on Orders or Picks.

exception DoesNotExist
exception MultipleObjectsReturned
class gardenhub.models.Garden(*args, **kwargs)

A whole landscape, divided into many plots. Managed by Garden Managers.

exception DoesNotExist
exception MultipleObjectsReturned
class gardenhub.models.Order(*args, **kwargs)

A request from a Gardener or Garden Manager to enlist a particular Plot for picking over a specified number of days.

exception DoesNotExist
exception MultipleObjectsReturned
get_picks()

Return the list of Picks that occurred on this Order’s plot within this Order’s timeframe.

get_status_icon()

Returns a Semantic UI status icon class string depending on this order’s status. This may be removed in the future.

is_active()

Whether this Order is active.

is_closed()

Whether this Order is closed.

is_open()

Whether this Order is open.

progress()

Percentage this order is complete, as a decimal between 0-100.

was_picked_today()

True if at least one Pick was submitted today for the Order’s Plot.

class gardenhub.models.Pick(*args, **kwargs)

A submission by a picker signifying that certain Crops have been picked from a particular Plot at a particular time.

exception DoesNotExist
exception MultipleObjectsReturned
inquirers()

People to notify about this pick.

class gardenhub.models.Plot(*args, **kwargs)

Subdivision of a Garden, allocated to a Gardener for growing food.

exception DoesNotExist
exception MultipleObjectsReturned
class gardenhub.models.User(*args, **kwargs)

Custom user class for GardenHub users. This is necessary because we want to authorize users by their email address (and provide a few extra fields).

exception DoesNotExist
exception MultipleObjectsReturned
can_edit_garden(garden)

Can the given user manage this garden? True if the user is listed in Garden.managers for that garden. False otherwise.

can_edit_order(order)

Can the given user manage this order? True if the user can edit Order.plot for that order. False otherwise.

can_edit_plot(plot)

Can the given user manage this plot? True if the user is listed in Plot.gardeners for that plot, OR the user is listed in Garden.managers for the garden in Plot.garden. False otherwise.

clean()

Hook for doing any extra model-wide validation after clean() has been called on every field by self.clean_fields. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field defined by NON_FIELD_ERRORS.

email_user(subject, message, from_email=None, **kwargs)

Send an email to this user.

get_full_name()

Return the first_name plus the last_name, with a space in between.

get_gardens()

Return all the Gardens the given user can edit.

get_orders()

Return all Orders for the given user’s Plots and Gardens.

get_peers()

Return all the Users within every Plot and Garden that you manage, except yourself.

get_picker_gardens()

Return all Gardens where the user is in Garden.picker.

get_picker_orders()

Return all Orders this user is assigned to fulfill.

get_plots()

Return all the Plots the given user can edit. Users can edit any plot which they are a gardener on, and any plot in a garden they manage.

get_short_name()

Return the short name for the user.

has_orders()

Determine whether the user has any orders at all.

is_anything()

GardenHub is only useful if the logged-in user can manage any garden or plot. If not, that is very sad. :(

is_garden_manager()

A garden manager is someone who facilitates renting Plots of a Garden out to Gardeners. Any person who is set as Garden.manager on at least one Garden.

is_gardener()

A gardener is someone who rents a garden Plot and grows food there. Gardeners are assigned to Plot.gardener on at least one Plot. GM’s of a garden with plots are also considered gardeners.

is_order_picker(order)

Is the user assigned as a picker on this order? True if the user is listed in Order.plot.garden.pickers. False otherwise.

is_picker()

A picker is someone who is assigned to fulfill Orders on a Garden. They will submit Picks over the duration of the Orders.

Order states

Orders are often queried by one of more possible states they can be in. Below is a chart of all the possible states.

State: open closed upcoming active inactive
start date Any Past Future Up through today Future
end date Future Past Future Today forward Past
canceled No Coerced No No Coerced
comparison and and and and or

Legend:

  • canceled = No means that if the order is canceled it cannot be this state.
  • canceled = Coerced means that the order will automatically be in this state by virtue of the fact it is canceled.
  • comparison describes how the start date and end date of the order are compared. For instance, an open order can have any start date and an end date in the future. An inactive order can have a start date in the future or an end date in the past.

QuerySet filtering

Corresponding to the table above, orders have custom QuerySet functions for each of the states. For instance, Order.objects.open() or Order.objects.active().

Views

class gardenhub.views.AccountRemoveView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.edit.DeletionMixin, django.views.generic.base.TemplateView

Remove the logged-in user’s GardenHub account.

delete(request, *args, **kwargs)

Call the delete() method on the fetched object and then redirect to the success URL.

class gardenhub.views.AccountSettingsView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.edit.FormView

Account settings screen for the logged-in user.

form_class

alias of gardenhub.forms.AccountSettingsForm

form_valid(form)

If the form is valid, redirect to the supplied URL.

class gardenhub.views.AccountView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.base.TemplateView

Profile edit screen for the logged-in user.

class gardenhub.views.ApiCrops(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, gardenhub.mixins.UserCanEditPlotMixin, django.views.generic.detail.DetailView

Return JSON about crops.

model

alias of gardenhub.models.Plot

class gardenhub.views.GardenDetailView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, gardenhub.mixins.UserCanEditGardenMixin, django.views.generic.detail.DetailView

View a single garden.

model

alias of gardenhub.models.Garden

class gardenhub.views.GardenListView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.list.ListView

A list of all gardens the logged-in user can edit.

get_queryset()

Return the list of items for this view.

The return value must be an iterable and may be an instance of QuerySet in which case QuerySet specific behavior will be enabled.

class gardenhub.views.GardenUpdateView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, gardenhub.mixins.UserCanEditGardenMixin, django.views.generic.edit.UpdateView

Edit form for an individual garden.

form_class

alias of gardenhub.forms.GardenForm

form_valid(form)

If the form is valid, save the associated model.

model

alias of gardenhub.models.Garden

class gardenhub.views.HomePageView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.base.TemplateView

Welcome screen with calls to action.

class gardenhub.views.LogoutView(**kwargs)

Bases: django.contrib.auth.views.LogoutView

Logs out the user and redirects them to the login screen.

class gardenhub.views.OrderCancelView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.contrib.auth.mixins.UserPassesTestMixin, django.views.generic.edit.DeleteView

delete(request, *args, **kwargs)

Call the delete() method on the fetched object and then redirect to the success URL.

model

alias of gardenhub.models.Order

class gardenhub.views.OrderCreateView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.contrib.auth.mixins.UserPassesTestMixin, django.views.generic.edit.CreateView

This is a form used to submit a new order. It’s used by gardeners, garden managers, or anyone who has the ability to edit a plot.

form_class

alias of gardenhub.forms.OrderForm

form_valid(form)

If the form is valid, save the associated model.

get_form(*args, **kwargs)

Return an instance of the form to be used in this view.

get_form_kwargs()

Return the keyword arguments for instantiating the form.

model

alias of gardenhub.models.Order

class gardenhub.views.OrderDetailView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.contrib.auth.mixins.UserPassesTestMixin, django.views.generic.detail.DetailView

Review an individual order that’s been submitted. Anyone who can edit the plot may view or cancel these orders.

model

alias of gardenhub.models.Order

class gardenhub.views.OrderListView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.list.ListView

Manage orders page to view all upcoming orders.

get_queryset()

Return the list of items for this view.

The return value must be an iterable and may be an instance of QuerySet in which case QuerySet specific behavior will be enabled.

class gardenhub.views.PasswordResetConfirmView(**kwargs)

Bases: django.contrib.auth.views.PasswordResetConfirmView

form_valid(form)

If the form is valid, redirect to the supplied URL.

class gardenhub.views.PasswordResetView(**kwargs)

Bases: django.contrib.auth.views.PasswordResetView

form_valid(form)

If the form is valid, redirect to the supplied URL.

class gardenhub.views.PickCreateView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.contrib.auth.mixins.UserPassesTestMixin, django.views.generic.edit.CreateView

Form enabling a picker to submit a Pick for a given plot.

form_valid(form)

If the form is valid, save the associated model.

get_context_data(**kwargs)

Insert the form into the context dict.

get_form_kwargs()

Return the keyword arguments for instantiating the form.

model

alias of gardenhub.models.Pick

class gardenhub.views.PlotCreateView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.contrib.auth.mixins.UserPassesTestMixin, django.views.generic.edit.CreateView

form_class

alias of gardenhub.forms.PlotForm

form_valid(form)

If the form is valid, save the associated model.

model

alias of gardenhub.models.Plot

class gardenhub.views.PlotListView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.list.ListView

A list of all plots the logged-in user can edit.

get_queryset()

Return the list of items for this view.

The return value must be an iterable and may be an instance of QuerySet in which case QuerySet specific behavior will be enabled.

class gardenhub.views.PlotUpdateView(**kwargs)

Bases: django.contrib.auth.mixins.LoginRequiredMixin, gardenhub.mixins.UserCanEditPlotMixin, django.views.generic.edit.UpdateView

Edit form for an individual plot.

form_class

alias of gardenhub.forms.PlotForm

form_valid(form)

If the form is valid, save the associated model.

get_form(*args, **kwargs)

Return an instance of the form to be used in this view.

model

alias of gardenhub.models.Plot

gardenhub.views.account_activate_view(request, token)

When a new user is invited, an email call to action will send them to this view so they can fill out their profile and activate their account.

Deployment (WIP)

This guide will walk you through deploying an instance of GardenHub online. It will make a lot of choices for you, with the trade-off of not requiring advanced knowledge in order to do this. Familiarity with the Linux terminal is required.

1. Getting the server

If you don’t have one, create an account with DigitalOcean. Then create a new droplet. It will cost you at least $10/mo, possibly more depending on the size of your userbase.

  • You will want to select the latest LTS, 64-bit Ubuntu as your OS. At the time of writing, that is 16.04 x64. The LTS version is the one that ends in .04, not .10.
  • Choose at least 2GB of memory if you intend to run this application seriously. If not, you may be able to get away with less. With many users, you will want to increase the memory so the app doesn’t become slow. You can always do this later.
  • Choose a datacenter region that is close to the majority of your users, if possible.
  • Enable IPv6.
  • Set the hostname to the domain name you intend to use, such as gardenhub.io.
  • You can leave the other options alone. Click “Create”.

Next, wait a minute for the droplet to provision. Once it’s done, you’ll get an IP address. You can use that to shell into the server via your terminal. Replace the IP address with your droplet’s:

ssh root@765.432.1

The password will be provided by DigitalOcean.

2. Provisioning the server

First, let’s install Dokku. It will let us push the GardenHub repo up to the server via git. While ssh’d into the server, run this:

wget https://raw.githubusercontent.com/dokku/dokku/v0.11.3/bootstrap.sh
sudo DOKKU_TAG=v0.11.3 bash bootstrap.sh

Once it completes, visit your server’s IP address in your web browser and follow the instructions. Be sure to tick the virtual hosts option and enter the domain name you intend to use with the app.

TODO: Write the rest of this

User Guide

This is a walkthrough for garden managers and administrators of the app. This guide will show you how to perform common administrative tasks.

Client view vs admin panel

There are two main parts of the site that are used to manage data: the client view and the admin panel.

There are some things that can only be accomplished within the client view, and others that can only be accomplished in the admin panel.

Client view

The client view is what end-users will see. This design is geared towards gardeners and pickers. It is the default view that will show when visiting the GardenHub app. In the client view, people can only see plots and gardens they are assigned to, even if they are an administrator. This is by design. Administrators are treated just like any other user in this view with no special capabilities.

The client view is the only place that users can invite other users to GardenHub via email.

_images/client.png

Admin panel

The admin panel is a tool for directly editing data in GardenHub’s database. You can use this to manually create users, manage gardens and plots, and edit the master list of crops. Administrators can access the entire site’s database through this view, regardless of whether they’re assigned to those gardens or plots.

_images/admin-panel.png

The admin panel can be accessed by first logging into GardenHub, then going under your user dropdown in the top navigation and clicking “Admin”. Note that only superusers and staff members can access the admin panel.

_images/admin-button.png

Inviting new users

For now, GardenHub is an invite-only platform. Anyone can invite other people to co-manage resources they can edit (such as plots or gardens). To invite a new user, you must first create the plot or garden you would like that user to manage, then add that user’s email address into the “managers” (or “gardeners”) field.

Email invitations only work in the client view.

Invite a new user to a plot

First, create a new plot, or edit an existing plot. Type the email address of the user you’d like to invite into the “gardeners” field. You will need to click away after so the email becomes enclosed in a gray box as shown below. Then you may click “Save plot” and an invitation will be sent to the email address(es) you entered.

Administrators must first add themselves to the plot before they can edit it in the client view.

_images/invite-user-plot.png

Invite a new user to a garden

First, create a new garden, or edit an existing garden. Type the email address of the user you’d like to invite into the “managers” field. You will need to click away after so the email becomes enclosed in a gray box as shown below. Then you may click “Save” and an invitation will be sent to the email address(es) you entered.

Administrators must first add themselves to the garden before they can edit it in the client view.

_images/invite-user-garden.png

Create a new user manually

If you’d like to create a new user without inviting them to a plot or garden, you can add them manually. They won’t receive an email invitation, so you will need to send them the password you choose.

First, visit the admin panel. You’ll need to be a superuser or staff user to do this:

_images/admin-button.png

Next, click the “Add” button next to “Users”:

_images/admin-add-user.png

Fill out the form. You must tick the “Active” box to allow the user to log in. You may also tick “Staff status” and “Superuser status” to grant this user admin privileges.

_images/admin-user-form.png

Finally, click “Save”. The user you created will now be able to log in. Be sure to assign them to any plots or gardens you’d like them to manage.

Managing plots/gardens

Adding yourself to a plot

If you want the ability to invite people via email to a plot, you will need to first assign yourself to that plot so you can access the plot within the client view.

  1. Visit the admin panel.
  2. Click “Plots”.
  3. Click the name of the plot you’d like to assign yourself to.
  4. Under the “Gardeners” field, find your name, and while holding the “Control” key (or “Command” key on MacOS), click it.
  5. Click “Save”.

Adding yourself to a garden

If you want the ability to invite people via email to a garden, you will need to first assign yourself to that garden so you can access the garden within the client view.

  1. Visit the admin panel.
  2. Click “Gardens”.
  3. Click the name of the garden you’d like to assign yourself to.
  4. Under the “Managers” field, find your name, and while holding the “Control” key (or “Command” key on MacOS), click it.
  5. Click “Save”.

Indices and tables