Commit be8d80f4 authored by David Haynes's avatar David Haynes 🙆

Merge branch '2.2-dev' into 'master'

2.2

Closes #97, #91, #92, #90, #88, #99, #100, #78, #75, and #77

See merge request !50
parents ec5b0b71 06ec7ed1
Pipeline #3761 passed with stage
in 1 minute and 6 seconds
...@@ -13,5 +13,4 @@ whats_open/secret_key.py ...@@ -13,5 +13,4 @@ whats_open/secret_key.py
whats_open/assets/ whats_open/assets/
static/admin/ static/admin/
data data
whats-open/api/migrations
.vscode .vscode
services: services:
- mysql:latest - mysql:5.7
variables: variables:
MYSQL_DATABASE: wopen MYSQL_DATABASE: wopen
...@@ -10,10 +10,13 @@ types: ...@@ -10,10 +10,13 @@ types:
before_script: before_script:
- apt-get update -qy - apt-get update -qy
- apt-get install -y mysql-client libmysqlclient-dev python-mysqldb libgdal1h libproj-dev proj-data proj-bin - apt-get install -y mysql-client default-libmysqlclient-dev python-mysqldb
- pip install -r requirements/test.txt gdal-bin libproj-dev proj-data proj-bin binutils
- cd whats-open/ - cd whats-open/
- export WOPEN_SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null) - pip install pipenv
- pipenv install --system --deploy
- export WOPEN_SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9"
| fold -w 60 | head -n1 2>/dev/null)
- export WOPEN_EMAIL_DOMAIN="@masonlive.gmu.edu" - export WOPEN_EMAIL_DOMAIN="@masonlive.gmu.edu"
- export WOPEN_DB_NAME="wopen" - export WOPEN_DB_NAME="wopen"
- export WOPEN_DB_USER="root" - export WOPEN_DB_USER="root"
...@@ -21,21 +24,16 @@ before_script: ...@@ -21,21 +24,16 @@ before_script:
- export WOPEN_DB_HOST="mysql" - export WOPEN_DB_HOST="mysql"
- export WOPEN_DB_PORT=3306 - export WOPEN_DB_PORT=3306
- export WOPEN_SUPERUSER=admin - export WOPEN_SUPERUSER=admin
- export WOPEN_ENV=dev
- python manage.py makemigrations - python manage.py makemigrations
- python manage.py makemigrations api - python manage.py makemigrations api
- python manage.py migrate - python manage.py migrate
- echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('root', 'root@srct.gmu.edu', 'root') " | python manage.py shell - echo "from django.contrib.auth import get_user_model; User =
get_user_model(); User.objects.create_superuser('root',
'root@srct.gmu.edu', 'root') " | python manage.py shell
whats-open-py3.5: whats-open-py3.7:
image: library/python:3.5 image: library/python:3.7
type: test type: test
script: script:
- python manage.py test - echo "Done 😄"
whats-open-py3.6:
image: library/python:3.6
type: test
script:
# - if pip list --outdated --format=legacy | grep "Latest" | wc -l > 0; then echo "Please update your dependecies!" && pip list --outdated --format=legacy && exit 1; else exit 0; fi
- coverage run --source=api --omit=*migrations/*,*admin.py,*__init__.py,*.pyc manage.py test
- coverage html -i && grep pc_cov htmlcov/index.html | egrep -o "[0-9]+\%" | awk '{ print "covered " $1;}'
...@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [2.2] - 2019-01-29
## Fixed
- Default owner now assigned automatically to the current logged in user / field hidden
- Path buggy schedule checking timezone utilization
- Schedules may now have N open times
## Added
- Admin action to bulk apply schedules to facilities
- Changed default image
- Alert model refactored to support URLs, subjects, and bodies
- Add front royal location as an option
## Removed
- Mason Korea support dropped
- Deprecated previous Alert model
- Drop label support
- Drop Sodoxo classifier
- Drop schedule promotion
- Drop schedule deletion
## [2.1.1] - 2017-01-13 ## [2.1.1] - 2017-01-13
## Fixed ## Fixed
...@@ -31,4 +55,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ...@@ -31,4 +55,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Special Schedules can start at a specific time on a date - Special Schedules can start at a specific time on a date
[2.1.0]: https://git.gmu.edu/srct/whats-open/compare/2.0...2.1 [2.1.0]: https://git.gmu.edu/srct/whats-open/compare/2.0...2.1
[2.1.1]: https://git.gmu.edu/srct/whats-open/compare/2.1...2.1.1 [2.1.1]: https://git.gmu.edu/srct/whats-open/compare/2.1...2.1.1
\ No newline at end of file
This diff is collapsed.
...@@ -3,12 +3,19 @@ ...@@ -3,12 +3,19 @@
############################################################ ############################################################
# Set the base image to Ubuntu # Set the base image to Ubuntu
FROM python:3.6 FROM python:3.7
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
# Update the sources list # Update the sources list and install all packages
RUN apt-get update # See: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
RUN apt-get install netcat libgdal1h libproj-dev proj-data proj-bin -y RUN apt-get update && apt-get install -y \
netcat \
libproj-dev \
proj-data \
proj-bin \
binutils \
gdal-bin \
&& rm -rf /var/lib/apt/lists/*
# Copy over all project files into /whats_open # Copy over all project files into /whats_open
RUN mkdir /whats-open/ RUN mkdir /whats-open/
...@@ -16,4 +23,5 @@ WORKDIR /whats-open/ ...@@ -16,4 +23,5 @@ WORKDIR /whats-open/
ADD . /whats-open/ ADD . /whats-open/
# Pip install all required dependecies # Pip install all required dependecies
RUN pip install -r /whats-open/requirements/base.txt RUN pip install pipenv
RUN pipenv install --system --deploy
\ No newline at end of file
...@@ -186,7 +186,7 @@ Apache License ...@@ -186,7 +186,7 @@ Apache License
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2017 George Mason University Student-Run Computing and Technology Copyright 2018 George Mason University Student-Run Computing and Technology
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
......
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
black = "*"
pylint = "*"
pylint-django = "*"
[packages]
django-autoslug-iplweb = "*"
django-cas-client = "==1.3.0"
djangorestframework = "==3.7.7"
django-model-utils = "==3.0.0"
mysqlclient = "==1.3.12"
django-taggit = "==0.22.2"
django-taggit-serializer = "==0.1.5"
djangorestframework-gis = "==0.12.0"
django-filter = "==1.0.4"
django-crispy-forms = "==1.7.0"
coreapi = "==2.3.3"
urllib3 = "==1.22"
docutils = "==0.13.1"
gunicorn = "*"
Django = "<2.1,>=2.0"
Markdown = "==2.6.10"
[requires]
python_version = "3.7"
[pipenv]
allow_prereleases = true
This diff is collapsed.
...@@ -23,10 +23,11 @@ contribute, so if you are struggling, or just want to learn, then we are ...@@ -23,10 +23,11 @@ contribute, so if you are struggling, or just want to learn, then we are
willing to help. willing to help.
Check out some of the other What's Open projects! Check out some of the other What's Open projects!
- https://git.gmu.edu/srct/whats-open-android
- https://git.gmu.edu/srct/whats-open-ios - https://git.gmu.edu/srct/whats-open-android
- https://git.gmu.edu/srct/whats-open-web - https://git.gmu.edu/srct/whats-open-ios
- https://git.gmu.edu/srct/whats-open-alexa - https://git.gmu.edu/srct/whats-open-web
- https://git.gmu.edu/srct/whats-open-alexa
# Setup instructions for local development # Setup instructions for local development
...@@ -113,9 +114,9 @@ environments across machines. ...@@ -113,9 +114,9 @@ environments across machines.
Installing Docker on your system: Installing Docker on your system:
- For macOS: https://docs.docker.com/docker-for-mac/ - For macOS: https://docs.docker.com/docker-for-mac/
- For Windows: https://docs.docker.com/docker-for-windows/ - For Windows: https://docs.docker.com/docker-for-windows/
- For your specific \*nix distro: https://docs.docker.com/engine/installation/ - For your specific \*nix distro: https://docs.docker.com/engine/installation/
Additionally, you will need to install docker-compose: https://docs.docker.com/compose/install/ Additionally, you will need to install docker-compose: https://docs.docker.com/compose/install/
...@@ -148,9 +149,10 @@ pass: admin ...@@ -148,9 +149,10 @@ pass: admin
Manual Setup involves all of the same steps as Docker, but just done manually. Manual Setup involves all of the same steps as Docker, but just done manually.
First, install python, pip, and virtualenv on your system. First, install python, pip, and virtualenv on your system.
* `python` is the programming language used for Django, the web framework used by whats-open.
* `pip` is the python package manager. - `python` is the programming language used for Django, the web framework used by whats-open.
* `virtualenv` allows you to isolate pip packages within virtual environments - `pip` is the python package manager.
- `virtualenv` allows you to isolate pip packages within virtual environments
Open a terminal and run the following command: Open a terminal and run the following command:
...@@ -167,10 +169,10 @@ You will also need the following `gdal` packages for GeoDjango support: ...@@ -167,10 +169,10 @@ You will also need the following `gdal` packages for GeoDjango support:
``` ```
sudo add-apt-repository -y ppa:ubuntugis/ubuntugis-unstable sudo add-apt-repository -y ppa:ubuntugis/ubuntugis-unstable
sudo apt update sudo apt update
sudo apt upgrade # if you already have gdal 1.11 installed sudo apt upgrade # if you already have gdal 1.11 installed
sudo apt install gdal-bin python-gdal python3-gdal # if you don't have gdal 1.11 already installed sudo apt install gdal-bin python-gdal python3-gdal # if you don't have gdal 1.11 already installed
``` ```
#### Database Setup #### Database Setup
...@@ -216,18 +218,17 @@ Run: ...@@ -216,18 +218,17 @@ Run:
GRANT ALL ON test_wopen.* TO 'wopen'@'localhost'; FLUSH PRIVILEGES; GRANT ALL ON test_wopen.* TO 'wopen'@'localhost'; FLUSH PRIVILEGES;
When running test cases, django creates a test database so your 'real' database When running test cases, django creates a test database so your 'real' database
doesn't get screwed up. This database is called 'test_' + whatever your normal doesn't get screwed up. This database is called 'test\_' + whatever your normal
database is named. Note that for permissions it doesn't matter that this database database is named. Note that for permissions it doesn't matter that this database
hasn't yet been created. hasn't yet been created.
The .* is to grant access all tables in the database, and 'flush privileges' The .\* is to grant access all tables in the database, and 'flush privileges'
reloads privileges to ensure that your user is ready to go. reloads privileges to ensure that your user is ready to go.
Exit the mysql shell by typing: Exit the mysql shell by typing:
exit exit
At this point we will need to set some environment variables. At this point we will need to set some environment variables.
If you are using bash then just copy paste the following into your terminal: If you are using bash then just copy paste the following into your terminal:
......
version: '3' version: "3"
services: services:
db: db:
image: mysql image: mysql:5.7
deploy: command: mysqld --character-set-server=utf8mb4
replicas: 1 --collation-server=utf8mb4_unicode_ci
restart_policy:
condition: on-failure
networks:
- wopen_net
ports: ports:
- "3306:3306" - "3306:3306"
environment: environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
MYSQL_DATABASE: wopen MYSQL_DATABASE: wopen
MYSQL_USER: wopen MYSQL_USER: wopen
MYSQL_PASSWORD: wopen MYSQL_PASSWORD: wopen
api: api:
image: whats-open-api build: .
deploy:
replicas: 1
restart_policy:
condition: on-failure
networks:
- wopen_net
ports: ports:
- '8000:8000' - "8000:8000"
command: /bin/bash ./startup.sh command: ./docker-startup.sh
volumes: volumes:
- .:/whats-open - .:/whats-open
depends_on: depends_on:
...@@ -39,6 +30,4 @@ services: ...@@ -39,6 +30,4 @@ services:
- WOPEN_DB_HOST=db - WOPEN_DB_HOST=db
- WOPEN_DB_PORT=3306 - WOPEN_DB_PORT=3306
- WOPEN_SUPERUSER=admin - WOPEN_SUPERUSER=admin
- WOPEN_ENV="dev"
networks:
wopen_net:
\ No newline at end of file
...@@ -6,9 +6,8 @@ done ...@@ -6,9 +6,8 @@ done
export WOPEN_SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null) export WOPEN_SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null)
python whats-open/manage.py flush --no-input
python whats-open/manage.py makemigrations python whats-open/manage.py makemigrations
python whats-open/manage.py makemigrations api python whats-open/manage.py makemigrations api
python whats-open/manage.py migrate python whats-open/manage.py migrate
echo "from django.contrib.auth.models import User; User.objects.filter(email='$WOPEN_SUPERUSER$WOPEN_EMAIL_DOMAIN').delete(); User.objects.create_superuser('$WOPEN_SUPERUSER$WOPEN_EMAIL_DOMAIN', '$WOPEN_SUPERUSER', 'admin')" | python whats-open/manage.py shell echo "from django.contrib.auth.models import User; User.objects.filter(username='$WOPEN_SUPERUSER$WOPEN_EMAIL_DOMAIN').delete(); User.objects.create_superuser('$WOPEN_SUPERUSER$WOPEN_EMAIL_DOMAIN', '$WOPEN_SUPERUSER', 'admin')" | python whats-open/manage.py shell
python whats-open/manage.py runserver 0.0.0.0:8000 python whats-open/manage.py runserver 0.0.0.0:8000
# This file is here because many Platforms as a Service look for
# requirements.txt in the root directory of a project.
-r requirements/production.txt
Django >= 2.0, < 2.1
django-autoslug-iplweb
django-cas-client==1.3.0
djangorestframework==3.7.7
django-model-utils==3.0.0
mysqlclient==1.3.12
setuptools==36.2.0
django-taggit==0.22.2
django-taggit-serializer==0.1.5
djangorestframework-gis==0.12.0
django-filter==1.0.4
django-crispy-forms==1.7.0
markdown==2.6.10
coreapi==2.3.3
urllib3==1.22
docutils==0.13.1
\ No newline at end of file
# Local development dependencies go here
-r test.txt
# Pro-tip: Try not to put anything here. There should be no dependency in
# production that isn't in development.
-r base.txt
gunicorn
\ No newline at end of file
# Test dependencies go here.
-r base.txt
coverage==4.4.1
...@@ -9,10 +9,16 @@ https://docs.djangoproject.com/en/1.11/ref/contrib/admin/ ...@@ -9,10 +9,16 @@ https://docs.djangoproject.com/en/1.11/ref/contrib/admin/
""" """
# Django Imports # Django Imports
from django.contrib import admin from django.contrib import admin
from django.contrib import messages
from django.contrib.gis.admin import OSMGeoAdmin from django.contrib.gis.admin import OSMGeoAdmin
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseRedirect
from django.shortcuts import render
# App Imports # App Imports
from .models import Facility, Schedule, OpenTime, Category, Location, Alert from .models import Facility, Schedule, OpenTime, Category, Location, Alert
@admin.register(Facility) @admin.register(Facility)
class FacilityAdmin(admin.ModelAdmin): class FacilityAdmin(admin.ModelAdmin):
""" """
...@@ -20,41 +26,141 @@ class FacilityAdmin(admin.ModelAdmin): ...@@ -20,41 +26,141 @@ class FacilityAdmin(admin.ModelAdmin):
Allows admins to create new facilities through the admin interface. Allows admins to create new facilities through the admin interface.
""" """
def drop_special_schedules(self, request, queryset):
num = queryset.count()
for facility in queryset:
facility.special_schedules.clear()
self.message_user(
request,
"Successfully cleared all special schedules for %d facilities." % num,
)
drop_special_schedules.short_description = (
"Clear all special schedules for selected facilities"
)
def assign_bulk_schedules(self, request, queryset):
num = queryset.count()
# all admin actions-related requests are post requests, so we're looking for
# the one that has the associated value with our confirmation input button
if "bulk_schedule" in request.POST:
try:
new_schedule = Schedule.objects.get(pk=request.POST["schedule"])
name = new_schedule.name
for facility in queryset:
facility.main_schedule = new_schedule
facility.save()
self.message_user(
request,
"Set %s as the main schedule for %d facilities." % (name, num),
)
except ObjectDoesNotExist:
self.message_user(
request,
"Unable to set a new main schedule for %d facilities." % num,
level=messages.ERROR,
)
return HttpResponseRedirect(request.get_full_path())
return render(
request,
"bulk_schedules.html",
context={"facilities": queryset, "schedules": Schedule.objects.all()},
)
assign_bulk_schedules.short_description = (
"Set a main schedule for selected facilities"
)
def assign_bulk_special_schedules(self, request, queryset):
num = queryset.count()
if "bulk_special_schedule" in request.POST:
try:
new_special_schedule = Schedule.objects.get(
pk=request.POST["special_schedule"]
)
name = new_special_schedule.name
for facility in queryset:
facility.special_schedules.add(new_special_schedule)
facility.save()
self.message_user(
request,
"Added %s as a special schedule to %d facilities." % (name, num),
)
except ObjectDoesNotExist:
self.message_user(
request,
"Unable to add additional special schedule to %d facilities." % num,
level=messages.ERROR,
)
return HttpResponseRedirect(request.get_full_path())
return render(
request,
"bulk_special_schedules.html",
context={"facilities": queryset, "schedules": Schedule.objects.all()},
)
assign_bulk_special_schedules.short_description = (
"Add a special schedule to selected facilities"
)
# a list of all actions to be added
actions = [
drop_special_schedules,
assign_bulk_schedules,
assign_bulk_special_schedules,
]
# Allow filtering by the following fields # Allow filtering by the following fields
list_filter = ['facility_category', 'facility_location'] list_filter = ["facility_category", "facility_location"]
list_display = ("facility_name", "main_schedule", "modified")
# Modify the rendered layout of the "create a new facility" page # Modify the rendered layout of the "create a new facility" page
# We are basically reordering things to look nicer to the user here # We are basically reordering things to look nicer to the user here
fieldsets = ( fieldsets = (
(None, { (
'fields': ('facility_name', 'logo', 'facility_category', None,
'facility_location', 'main_schedule', 'special_schedules', {
('facility_product_tags', 'facility_labels', "fields": (
'facility_classifier'), "facility_name",
'tapingo_url', 'phone_number', 'note', 'owners'), "logo",
}), "facility_category",
"facility_location",
"main_schedule",
"special_schedules",
("facility_product_tags", "facility_classifier"),
"tapingo_url",
"phone_number",
"note",
)
},
),
) )
autocomplete_fields = ["main_schedule", "special_schedules"]
# despite the name of this method, ("change" seems to imply it would affect modify)
# it is called only when initially creating a model
def get_changeform_initial_data(self, request):
initial_data = super(FacilityAdmin, self).get_changeform_initial_data(request)
initial_data["owners"] = [request.user]
return initial_data
class OpenTimeInline(admin.TabularInline): class OpenTimeInline(admin.StackedInline):
""" """
A table of time periods that represent an "open time" for a Facility. A table of time periods that represent an "open time" for a Facility.
https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.TabularInline https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.TabularInline
""" """
# Columns correspond to each attribute in the OpenTime table # Columns correspond to each attribute in the OpenTime table
model = OpenTime model = OpenTime
# 7 days of the week, so only have 7 rows extra = 1
max_num = 7
extra = 7
# We are basically reordering things to look nicer to the user here # We are basically reordering things to look nicer to the user here
fieldsets = ( fieldsets = (
(None, { (None, {"fields": (("start_day", "start_time"), ("end_day", "end_time"))}),
'fields': (
('start_day', 'start_time'),
('end_day', 'end_time')
),
}),
) )
@admin.register(Schedule) @admin.register(Schedule)
class ScheduleAdmin(admin.ModelAdmin): class ScheduleAdmin(admin.ModelAdmin):
""" """
...@@ -64,22 +170,28 @@ class ScheduleAdmin(admin.ModelAdmin): ...@@ -64,22 +170,28 @@ class ScheduleAdmin(admin.ModelAdmin):
Additionally, we append the OpenTimeInline table to allow for open times to Additionally, we append the OpenTimeInline table to allow for open times to
be defined for the schedule we are creating. be defined for the schedule we are creating.
""" """
# Allow filtering by the following fields # Allow filtering by the following fields
list_display = ['name', 'modified'] list_display = ["name", "modified"]
# Append the OpenTimeInline table to the end of our admin panel # Append the OpenTimeInline table to the end of our admin panel
inlines = [OpenTimeInline, ] inlines = [OpenTimeInline]
# Modify the rendered layout of the "create a new facility" page # Modify the rendered layout of the "create a new facility" page
fieldsets = ( fieldsets = (
(None, { (
'fields': ('name', None,
# Pair valid_start and valid_end together on the same {
# line "fields": (
('valid_start', 'valid_end'), "name",
'twenty_four_hours', # Pair valid_start and valid_end together on the same line
'schedule_for_removal', ("valid_start", "valid_end"),
'promote_to_main') "twenty_four_hours",
}), )
},
),
) )
search_fields = ["name"] # search terms for autcomplete
ordering = ["name"] # autocomplete ordering
# https://docs.djangoproject.com/en/1.11/ref/contrib/gis/admin/#osmgeoadmin # https://docs.djangoproject.com/en/1.11/ref/contrib/gis/admin/#osmgeoadmin
OSMGeoAdmin.default_lon = -8605757.16502 OSMGeoAdmin.default_lon = -8605757.16502
...@@ -89,3 +201,7 @@ admin.site.register(Location, OSMGeoAdmin) ...@@ -89,3 +201,7 @@ admin.site.register(Location, OSMGeoAdmin)
# Use the default ModelAdmin interface for these # Use the default ModelAdmin interface for these
admin.site.register(Category) admin.site.register(Category)
admin.site.register(Alert) admin.site.register(Alert)
admin.site.site_header = "What's Open API"
admin.site.site_title = "What's Open API"
admin.site.index_title = "Admin"
This diff is collapsed.
# Generated by Django 2.0.10 on 2019-01-26 18:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
]
operations = [
migrations.RemoveField(