Initial commit, converting laundry app to site
This commit is contained in:
commit
2e2ee19a6b
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
*~
|
||||||
|
*.log
|
||||||
|
*.pot
|
||||||
|
*.py[cod]
|
||||||
|
*.so
|
||||||
|
*.db
|
||||||
|
*.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.sw[on]
|
||||||
|
venv
|
||||||
|
apache
|
||||||
|
old
|
||||||
|
*db
|
||||||
|
laundry/secrets.py
|
||||||
|
static
|
||||||
|
*.svg
|
1
laundry.json
Normal file
1
laundry.json
Normal file
File diff suppressed because one or more lines are too long
0
laundry/__init__.py
Normal file
0
laundry/__init__.py
Normal file
166
laundry/settings.py
Normal file
166
laundry/settings.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# Django settings for laundry project.
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
|
ADMINS = (
|
||||||
|
('thallada', 'thallada@mit.edu'),
|
||||||
|
('tyler', 'tyler@hallada.net'),
|
||||||
|
)
|
||||||
|
|
||||||
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
|
PROJECT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': secrets.DATABASE_NAME,
|
||||||
|
'USER': secrets.DATABASE_USER,
|
||||||
|
'PASSWORD': secrets.DATABASE_PASSWORD,
|
||||||
|
'HOST': secrets.DATABASE_HOST,
|
||||||
|
'PORT': '', # Set to empty string for default. Not used with sqlite3.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||||
|
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
# Local time zone for this installation. Choices can be found here:
|
||||||
|
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||||
|
# although not all choices may be available on all operating systems.
|
||||||
|
# In a Windows environment this must be set to your system time zone.
|
||||||
|
TIME_ZONE = 'America/New_York'
|
||||||
|
|
||||||
|
# Language code for this installation. All choices can be found here:
|
||||||
|
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
# If you set this to False, Django will make some optimizations so as not
|
||||||
|
# to load the internationalization machinery.
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
# If you set this to False, Django will not format dates, numbers and
|
||||||
|
# calendars according to the current locale.
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
# If you set this to False, Django will not use timezone-aware datetimes.
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||||
|
# Example: "/var/www/example.com/media/"
|
||||||
|
MEDIA_ROOT = ''
|
||||||
|
|
||||||
|
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||||
|
# trailing slash.
|
||||||
|
# Examples: "http://example.com/media/", "http://media.example.com/"
|
||||||
|
MEDIA_URL = ''
|
||||||
|
|
||||||
|
# Absolute path to the directory static files should be collected to.
|
||||||
|
# Don't put anything in this directory yourself; store your static files
|
||||||
|
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||||
|
# Example: "/var/www/example.com/static/"
|
||||||
|
STATIC_ROOT = os.path.join(PROJECT_PATH, 'static')
|
||||||
|
|
||||||
|
# URL prefix for static files.
|
||||||
|
# Example: "http://example.com/static/", "http://static.example.com/"
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# Additional locations of static files
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
# Put strings here, like "/home/html/static" or "C:/www/django/static".
|
||||||
|
# Always use forward slashes, even on Windows.
|
||||||
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
|
os.path.join(PROJECT_PATH, 'laundry_app', 'static'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# List of finder classes that know how to find static files in
|
||||||
|
# various locations.
|
||||||
|
STATICFILES_FINDERS = (
|
||||||
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||||
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||||
|
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make this unique, and don't share it with anybody.
|
||||||
|
SECRET_KEY = secrets.SECRET_KEY
|
||||||
|
|
||||||
|
# List of callables that know how to import templates from various sources.
|
||||||
|
TEMPLATE_LOADERS = (
|
||||||
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
'django.template.loaders.app_directories.Loader',
|
||||||
|
# 'django.template.loaders.eggs.Loader',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
# Uncomment the next line for simple clickjacking protection:
|
||||||
|
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'laundry.urls'
|
||||||
|
|
||||||
|
# Python dotted path to the WSGI application used by Django's runserver.
|
||||||
|
WSGI_APPLICATION = 'laundry.wsgi.application'
|
||||||
|
|
||||||
|
TEMPLATE_DIRS = (
|
||||||
|
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
||||||
|
# Always use forward slashes, even on Windows.
|
||||||
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
|
os.path.join(PROJECT_PATH, 'templates'),
|
||||||
|
)
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.sites',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
# Uncomment the next line to enable the admin:
|
||||||
|
# 'django.contrib.admin',
|
||||||
|
# Uncomment the next line to enable admin documentation:
|
||||||
|
# 'django.contrib.admindocs',
|
||||||
|
'laundry_app',
|
||||||
|
'south',
|
||||||
|
)
|
||||||
|
|
||||||
|
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
|
||||||
|
|
||||||
|
# A sample logging configuration. The only tangible logging
|
||||||
|
# performed by this configuration is to send an email to
|
||||||
|
# the site admins on every HTTP 500 error when DEBUG=False.
|
||||||
|
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||||
|
# more details on how to customize your logging configuration.
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'filters': {
|
||||||
|
'require_debug_false': {
|
||||||
|
'()': 'django.utils.log.RequireDebugFalse'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'mail_admins': {
|
||||||
|
'level': 'ERROR',
|
||||||
|
'filters': ['require_debug_false'],
|
||||||
|
'class': 'django.utils.log.AdminEmailHandler'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'django.request': {
|
||||||
|
'handlers': ['mail_admins'],
|
||||||
|
'level': 'ERROR',
|
||||||
|
'propagate': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
18
laundry/urls.py
Normal file
18
laundry/urls.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.conf.urls import patterns, include, url
|
||||||
|
|
||||||
|
# Uncomment the next two lines to enable the admin:
|
||||||
|
from django.contrib import admin
|
||||||
|
admin.autodiscover()
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
# Examples:
|
||||||
|
# url(r'^$', 'laundry.views.home', name='home'),
|
||||||
|
# url(r'^laundry/', include('laundry.foo.urls')),
|
||||||
|
|
||||||
|
# Uncomment the admin/doc line below to enable admin documentation:
|
||||||
|
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
|
|
||||||
|
# Uncomment the next line to enable the admin:
|
||||||
|
# url(r'^admin/', include(admin.site.urls)),
|
||||||
|
url(r'', include('laundry_app.urls')),
|
||||||
|
)
|
32
laundry/wsgi.py
Normal file
32
laundry/wsgi.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for laundry project.
|
||||||
|
|
||||||
|
This module contains the WSGI application used by Django's development server
|
||||||
|
and any production WSGI deployments. It should expose a module-level variable
|
||||||
|
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
|
||||||
|
this application via the ``WSGI_APPLICATION`` setting.
|
||||||
|
|
||||||
|
Usually you will have the standard Django WSGI application here, but it also
|
||||||
|
might make sense to replace the whole Django WSGI application with a custom one
|
||||||
|
that later delegates to the Django one. For example, you could introduce WSGI
|
||||||
|
middleware here, or combine a Django application with an application of another
|
||||||
|
framework.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||||
|
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||||
|
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||||
|
# os.environ["DJANGO_SETTINGS_MODULE"] = "laundry.settings"
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "laundry.settings")
|
||||||
|
|
||||||
|
# This application object is used by any WSGI server configured to use this
|
||||||
|
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||||
|
# setting points here.
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
application = get_wsgi_application()
|
||||||
|
|
||||||
|
# Apply WSGI middleware here.
|
||||||
|
# from helloworld.wsgi import HelloWorldApplication
|
||||||
|
# application = HelloWorldApplication(application)
|
0
laundry_app/__init__.py
Normal file
0
laundry_app/__init__.py
Normal file
120
laundry_app/laundry.py
Normal file
120
laundry_app/laundry.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
from urllib2 import urlopen
|
||||||
|
import pygal
|
||||||
|
from pygal.style import Style
|
||||||
|
from models import LaundryMachine, LaundryRecord, Timeslot, LaundrySummary
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
WASHER = LaundryMachine.WASHER
|
||||||
|
DRYER = LaundryMachine.DRYER
|
||||||
|
AVAILABLE = LaundryRecord.AVAILABLE
|
||||||
|
IN_USE = LaundryRecord.IN_USE
|
||||||
|
CYCLE_COMPLETE = LaundryRecord.CYCLE_COMPLETE
|
||||||
|
UNAVAILABLE = LaundryRecord.UNAVAILABLE
|
||||||
|
BASE_URL_QUERY = 'http://gmu.esuds.net/RoomStatus/machineStatus.i?bottomLocationId='
|
||||||
|
|
||||||
|
def load_data(hall):
|
||||||
|
"""Extract table data from esuds for specified hall"""
|
||||||
|
htsl = urlopen(BASE_URL_QUERY+str(int(hall.location_id))).read()
|
||||||
|
soup = BeautifulSoup(htsl) # Start cook'n
|
||||||
|
rows = [row for row in soup.findAll('tr')[1:] if len(row('td')) > 1]
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def get_num_machines_per_status(status, records):
|
||||||
|
"""
|
||||||
|
Count the number of machines in a hall which have the desired status and
|
||||||
|
return a list that can be inputed to the pygal add series function.
|
||||||
|
|
||||||
|
Returns a list of two ints. The first element is the number of washers
|
||||||
|
with the desired status and the second is the number of dryers.
|
||||||
|
"""
|
||||||
|
return [len(filter(lambda r: r.machine.type == WASHER and
|
||||||
|
r.availability == status, records)),
|
||||||
|
len(filter(lambda r: r.machine.type == DRYER and
|
||||||
|
r.availability == status, records))]
|
||||||
|
|
||||||
|
def generate_current_chart(filepath, records, hall):
|
||||||
|
"""
|
||||||
|
Generate stacked bar chart of current laundry usage for specified hall and
|
||||||
|
save svg at filepath.
|
||||||
|
"""
|
||||||
|
custom_style = Style(colors=('#B6E354', '#FF5995', '#FEED6C', '#E41B17'))
|
||||||
|
chart = pygal.StackedBar(style=custom_style, width=800, height=512, explicit_size=True)
|
||||||
|
chart.title = 'Current laundry machine usage in ' + hall.name
|
||||||
|
chart.x_labels = ['Washers', 'Dryers']
|
||||||
|
chart.add('Available', get_num_machines_per_status(AVAILABLE, records))
|
||||||
|
chart.add('In Use', get_num_machines_per_status(IN_USE, records))
|
||||||
|
chart.add('Cycle Complete', get_num_machines_per_status(CYCLE_COMPLETE, records))
|
||||||
|
chart.add('Unavailable', get_num_machines_per_status(UNAVAILABLE, records))
|
||||||
|
chart.range = [0, 11]
|
||||||
|
chart.render_to_file(filepath)
|
||||||
|
|
||||||
|
# NOTE: Abandoning generating the weekly chart via mysql and Django for now.
|
||||||
|
# (cron script and csv file is just easier) Sorry if there are a lot of
|
||||||
|
# skeletons lying around as a result of this abandonment.
|
||||||
|
#def generate_weekly_chart(filepath, hall):
|
||||||
|
# First, add a new LaundrySummary for the most recent record.
|
||||||
|
# TODO: determine if this step actually needs to be done or not.
|
||||||
|
#records = list()
|
||||||
|
#for machine in hall.machines:
|
||||||
|
#records.append(machine.get_latest_record())
|
||||||
|
#ts = records[0].timeslot
|
||||||
|
#ts = ts - datetime.timedelta(minutes=ts.minute % 15,
|
||||||
|
#seconds=ts.second, microseconds=ts.microsecond)
|
||||||
|
#day = ts.weekday()
|
||||||
|
#slot = Timeslot.objects.get_or_create(hall=hall, time=ts.time(), day=day)
|
||||||
|
#LaundrySummary.objects.create(timeslot=slot,
|
||||||
|
#washers = len([w for w in records if
|
||||||
|
#(w.machine.type == LaundryMachine.WASHER and
|
||||||
|
#w.availability == LaundryRecord.AVAILABLE)]),
|
||||||
|
#dryers = len([w for w in records if
|
||||||
|
#(w.machine.type == LaundryMachine.DRYER and
|
||||||
|
#w.availability == LaundryRecord.AVAILABLE)]),
|
||||||
|
#)
|
||||||
|
# Now, actually generate the chart by adding all of the LaundrySummaries
|
||||||
|
# to the chart and making all of the Timeslots the x-axis.
|
||||||
|
|
||||||
|
def update(hall, filepath=None):
|
||||||
|
"""
|
||||||
|
Scrape data from esuds for the specified hall, create a new LaundryRecord
|
||||||
|
for each machine, and optionally save a new current chart for the hall.
|
||||||
|
|
||||||
|
To generate a current chart with this update, set filepath to a valid
|
||||||
|
filepath to save and svg file.
|
||||||
|
"""
|
||||||
|
rows = load_data(hall)
|
||||||
|
records = list()
|
||||||
|
for row in rows:
|
||||||
|
cells = row('td')
|
||||||
|
# Some small laundry rooms (with only two machines) do not label their
|
||||||
|
# machines. Our database represents "unlabeled" as 0. We will just have
|
||||||
|
# to rely on the type of the machine when searching.
|
||||||
|
if str(cells[1].contents[0]) == "unlabeled":
|
||||||
|
number = 0
|
||||||
|
else:
|
||||||
|
number = int(cells[1].contents[0])
|
||||||
|
type = cells[2].contents[0]
|
||||||
|
# For some reason there is a distinction between normal and "stacked"
|
||||||
|
# washers/dryers in esuds; we only need normal, so cut "stacked" off.
|
||||||
|
if type == 'Stacked Dryer': type = type[8:]
|
||||||
|
if type == 'Stacked Washer': type = type[8:]
|
||||||
|
# Since Presidents Park - Harrison apparently has freak Washer/Dryer
|
||||||
|
# hybrids, we will just ignore the type field when getting the machine.
|
||||||
|
if type == 'Stacked Washer/Dryer': type = None
|
||||||
|
availability = cells[3].contents[1].contents[0]
|
||||||
|
time_remaining = None
|
||||||
|
if cells[4].contents[0] != ' ':
|
||||||
|
time_remaining = int(cells[4].contents[0])
|
||||||
|
if type:
|
||||||
|
machine = LaundryMachine.objects.get(number=number, type=type,
|
||||||
|
hall=hall)
|
||||||
|
else: # Search without type, for Presidents Park - Harrison
|
||||||
|
machine = LaundryMachine.objects.get(number=number, hall=hall)
|
||||||
|
record = LaundryRecord(machine=machine, availability=availability,
|
||||||
|
time_remaining=time_remaining)
|
||||||
|
if filepath:
|
||||||
|
records.append(record)
|
||||||
|
else:
|
||||||
|
record.save()
|
||||||
|
if filepath:
|
||||||
|
generate_current_chart(filepath, records, hall)
|
54
laundry_app/models.py
Normal file
54
laundry_app/models.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Hall(models.Model):
|
||||||
|
name = models.TextField(max_length=100)
|
||||||
|
location_id = models.IntegerField() # id used by esuds
|
||||||
|
|
||||||
|
class LaundryMachine(models.Model):
|
||||||
|
WASHER = 'Washer'
|
||||||
|
DRYER = 'Dryer'
|
||||||
|
MACHINE_TYPES = (
|
||||||
|
('Washer', 'Washer'),
|
||||||
|
('Dryer', 'Dryer'),
|
||||||
|
)
|
||||||
|
number = models.PositiveSmallIntegerField() # number assigned by esuds
|
||||||
|
type = models.TextField(choices=MACHINE_TYPES)
|
||||||
|
hall = models.ForeignKey('Hall', related_name='machine')
|
||||||
|
|
||||||
|
def get_latest_record(self):
|
||||||
|
records = self.records.all()
|
||||||
|
return records[len(records)-1]
|
||||||
|
|
||||||
|
class LaundryRecord(models.Model):
|
||||||
|
AVAILABLE = 'Available'
|
||||||
|
IN_USE = 'In Use'
|
||||||
|
CYCLE_COMPLETE = 'Cycle Complete'
|
||||||
|
UNAVAILABLE = 'Unavailable'
|
||||||
|
AVAILABILITIES = (
|
||||||
|
(AVAILABLE, 'Available'),
|
||||||
|
(IN_USE, 'In Use'),
|
||||||
|
(CYCLE_COMPLETE, 'Cycle Complete'),
|
||||||
|
(UNAVAILABLE, 'Unavailable'),
|
||||||
|
)
|
||||||
|
machine = models.ForeignKey('LaundryMachine', related_name='records')
|
||||||
|
availability = models.TextField(choices=AVAILABILITIES)
|
||||||
|
time_remaining = models.IntegerField(null=True)
|
||||||
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Timeslot(models.Model):
|
||||||
|
hall = models.ForeignKey('Hall', related_name='timeslots')
|
||||||
|
day = models.SmallIntegerField()
|
||||||
|
time = models.TimeField()
|
||||||
|
|
||||||
|
def washer_avg(self):
|
||||||
|
summaries = self.summaries.all()
|
||||||
|
return (sum(s.washers for s in summaries) / (len(summaries)) or 1)
|
||||||
|
|
||||||
|
def dryer_avg(self):
|
||||||
|
summaries = self.summaries.all()
|
||||||
|
return (sum(s.dryers for s in summaries) / (len(summaries)) or 1)
|
||||||
|
|
||||||
|
class LaundrySummary(models.Model):
|
||||||
|
timeslot = models.ForeignKey('Timeslot', related_name='summaries')
|
||||||
|
washers = models.IntegerField()
|
||||||
|
dryers = models.IntegerField()
|
16
laundry_app/tests.py
Normal file
16
laundry_app/tests.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
This file demonstrates writing tests using the unittest module. These will pass
|
||||||
|
when you run "manage.py test".
|
||||||
|
|
||||||
|
Replace this with more appropriate tests for your application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleTest(TestCase):
|
||||||
|
def test_basic_addition(self):
|
||||||
|
"""
|
||||||
|
Tests that 1 + 1 always equals 2.
|
||||||
|
"""
|
||||||
|
self.assertEqual(1 + 1, 2)
|
6
laundry_app/urls.py
Normal file
6
laundry_app/urls.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
|
||||||
|
urlpatterns = patterns('laundry_app.views',
|
||||||
|
url(r'^ajax/current/(?P<hall>\d+)/$', 'ajax_get_current', name='get_current_chart'),
|
||||||
|
url(r'^$', 'main_page', name='laundry_main'),
|
||||||
|
)
|
29
laundry_app/views.py
Normal file
29
laundry_app/views.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from django.template import RequestContext
|
||||||
|
from django.shortcuts import render_to_response, get_object_or_404
|
||||||
|
from models import Hall
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from os.path import join
|
||||||
|
import laundry
|
||||||
|
|
||||||
|
SVG_DIR = join(settings.PROJECT_PATH, 'laundry_app', 'static')
|
||||||
|
SVG_URL = settings.STATIC_URL
|
||||||
|
|
||||||
|
def main_page(request):
|
||||||
|
# pass the halls to the html, and let cookies/user decide which to pick
|
||||||
|
halls = [(hall.name, hall.id) for hall in
|
||||||
|
Hall.objects.all().order_by('name')]
|
||||||
|
return render_to_response('laundry/main.html', {
|
||||||
|
'halls': halls,
|
||||||
|
},
|
||||||
|
context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
def ajax_get_current(request, hall):
|
||||||
|
hall_obj = get_object_or_404(Hall, pk=hall)
|
||||||
|
filename = str(hall_obj.id) + '_current.svg'
|
||||||
|
try:
|
||||||
|
laundry.update(hall_obj, filepath=join(SVG_DIR, filename))
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return HttpResponse(status=500);
|
||||||
|
return HttpResponse(join(SVG_URL, filename))
|
10
manage.py
Executable file
10
manage.py
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "laundry.settings")
|
||||||
|
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
execute_from_command_line(sys.argv)
|
65
templates/laundry/main.html
Normal file
65
templates/laundry/main.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
{% block head %}
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
{% block title %}<title>GMU Laundry</title>{% endblock %}
|
||||||
|
{% block description %}
|
||||||
|
<meta name="Description" content="Current laundry machine usage charts for George Mason University's dorms. An experiment by Tyler Hallada.">
|
||||||
|
{% endblock %}
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="{{ STATIC_URL }}css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="{{ STATIC_URL }}css/font-awesome.css" rel="stylesheet">
|
||||||
|
<link href="{{ STATIC_URL }}css/bootstrap-responsive.min.css" rel="stylesheet">
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/bootstrap.min.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/laundry.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/jquery.cookie.js"></script>
|
||||||
|
<link href="{{ STATIC_URL }}img/glyphicons-halflings.png" rel="icons">
|
||||||
|
<link href="{{ STATIC_URL }}css/style.css" rel="stylesheet">
|
||||||
|
<!-- Creative Commons - Attribution - The Noun Project -->
|
||||||
|
<link href="/favicon.ico" rel="shortcut icon">
|
||||||
|
{% block head-extra %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
{% endblock %}
|
||||||
|
<script>
|
||||||
|
var halls = {
|
||||||
|
{% for h in halls %}
|
||||||
|
"{{ h.0 }}" : {{ h.1 }},
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background:#000000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body class="laundry">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row hall-select-container">
|
||||||
|
<select id="hall-select">
|
||||||
|
{% for h in halls %}
|
||||||
|
{% if h.1 == 1 %}
|
||||||
|
<option selected="selected">{{ h.0 }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option>{{ h.0 }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row chart-container">
|
||||||
|
<div class="current-chart"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-39880341-1', 'hallada.net');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user