Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ env:
- CONTAINER: request-broker-web
- APPLICATION_NAME: request_broker
- APPLICATION_PORT: 80
- TRAVIS_CI: true
before_install:
- cp ${APPLICATION_NAME}/config.py.example ${APPLICATION_NAME}/config.py
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,21 @@ Or, if you want to remove all data

## Configuration

The request broker manages configuration by setting environment variables. These variables can be seen in `docker-compose.yml`.

Deployment using the `docker-compose.prod.yml` or `docker-compose.dev.yml` files requires the presence of an `.env.prod` or `.env.dev` file in the root directory of the application. The environment variables included in those files should match the variables in `docker-compose.yml`, although the values assigned to those variables may change.
The request broker manages most configuration through environment variables. These
variables can be seen in `docker-compose.yml`.

Configuration for request defaults is managed in the Django Admin UI. To access
this UI, you'll need to log in to the Admin backend (located at `/admin/`)
with a user account with the `is_staff` boolean set to `True`. If you're running
this application in a container and have the `DJANGO_SUPERUSER_USERNAME`,
`DJANGO_SUPERUSER_PASSWORD` and `DJANGO_SUPERUSER_EMAIL` variables set, a user
will be created on your behalf if a user with that username does not already exist.

Deployment using the `docker-compose.prod.yml` or `docker-compose.dev.yml` files
requires the presence of an `.env.prod` or `.env.dev` file in the root directory
of the application. The environment variables included in those files should
match the variables in `docker-compose.yml`, although the values assigned to
those variables may change.

## Services

Expand Down
17 changes: 17 additions & 0 deletions create_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from process_request.models import User

if all([
getattr(settings, 'DJANGO_SUPERUSER_USERNAME', None),
getattr(settings, 'DJANGO_SUPERUSER_PASSWORD', None),
getattr(settings, 'DJANGO_SUPERUSER_EMAIL', None)]):
if not User.objects.filter(username=settings.DJANGO_SUPERUSER_USERNAME).exists():
User.objects.create_superuser(
username=settings.DJANGO_SUPERUSER_USERNAME,
password=settings.DJANGO_SUPERUSER_PASSWORD,
email=settings.DJANGO_SUPERUSER_EMAIL,
is_staff=True)
else:
raise ImproperlyConfigured("Values for DJANGO_SUPERUSER_USERNAME, DJANGO_SUPERUSER_PASSWORD and DJANGO_SUPERUSER_EMAIL must be set in config.py")
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ services:
entrypoint: /code/entrypoint.sh
environment:
- APPLICATION_PORT=${APPLICATION_PORT:-8000}
- TRAVIS_CI=${TRAVIS_CI}
volumes:
- .:/code
ports:
Expand Down
9 changes: 9 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

set -e

echo "Waiting for PostgreSQL..."

# Create config.py if it doesn't exist
Expand All @@ -11,6 +13,13 @@ fi
./wait-for-it.sh db:5432 -- echo "Apply database migrations"
python manage.py migrate

# Create default users when running locally
# If this blows up, you likely need to add a DJANGO_SUPERUSER_USERNAME to config.py
if [[ -z "${TRAVIS_CI}" ]]; then
echo "Creating default users"
python manage.py shell < ./create_users.py
fi

#Start server
echo "Starting server"
python manage.py runserver 0.0.0.0:${APPLICATION_PORT}
20 changes: 18 additions & 2 deletions process_request/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# from django.contrib import admin
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

# Register your models here.
from .models import ConfigList, ConfigPair, User


class ConfigPairInline(admin.TabularInline):
fields = ('key', 'value', 'is_request_data_key')
model = ConfigPair


@admin.register(ConfigList)
class ConfigListAdmin(admin.ModelAdmin):
inlines = [ConfigPairInline]


@admin.register(User)
class UserAdmin(UserAdmin):
pass
31 changes: 31 additions & 0 deletions process_request/migrations/0004_configlist_configpair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.0.7 on 2022-09-09 18:22

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('process_request', '0003_auto_20211106_1625'),
]

operations = [
migrations.CreateModel(
name='ConfigList',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='ConfigPair',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(help_text='Key for the configuration.', max_length=255)),
('value', models.CharField(help_text='Value to assign to the configuration key.', max_length=255)),
('is_request_data_key', models.BooleanField(default=False, help_text='If checked, uses the value provided as a key to get a value from the request data.')),
('config_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='process_request.configlist')),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.0.7 on 2022-09-09 18:40

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('process_request', '0004_configlist_configpair'),
]

operations = [
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(blank=True, max_length=254, verbose_name='email address'),
),
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
migrations.AlterField(
model_name='user',
name='last_name',
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
),
]
61 changes: 61 additions & 0 deletions process_request/migrations/0006_auto_20220909_1842.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Generated by Django 4.0.7 on 2022-09-09 18:42

from django.db import migrations

CONFIGURATIONS = [
{"Duplication Defaults": [
("WebRequestForm", "PhotoduplicationRequest", False),
("RequestType", "Copy", False),
("Format", "format", True),
("SpecialRequest", "questions", True),
("SkipOrderEstimate", "Yes", False),
]},
{"Reading Room Defaults": [
("WebRequestForm", "DefaultRequest", False),
("RequestType", "Loan", False),
("ScheduledDate", "scheduledDate", True),
("SpecialRequest", "questions", True)
]},
{"Request Defaults": [
("AeonForm", "EADRequest", False),
("DocumentType", "Default", False),
("GroupingIdentifier", "GroupingField", False),
("GroupingOption_ItemInfo1", "Concatenate", False),
("GroupingOption_ItemDate", "Concatenate", False),
("GroupingOption_ItemTitle", "FirstValue", False),
("GroupingOption_ItemAuthor", "FirstValue", False),
("GroupingOption_ItemSubtitle", "FirstValue", False),
("GroupingOption_ItemVolume", "FirstValue", False),
("GroupingOption_ItemIssue", "Concatenate", False),
("GroupingOption_ItemInfo2", "Concatenate", False),
("GroupingOption_CallNumber", "FirstValue", False),
("GroupingOption_ItemInfo3", "FirstValue", False),
("GroupingOption_ItemCitation", "FirstValue", False),
("GroupingOption_ItemNumber", "FirstValue", False),
("GroupingOption_Location", "FirstValue", False),
("GroupingOption_ItemInfo5", "FirstValue", False),
("UserReview", "No", False),
("SubmitButton", "Submit Request", False),
]}
]


def add_configurations(apps, schema_editor):
ConfigList = apps.get_model('process_request', 'ConfigList')
ConfigPair = apps.get_model('process_request', 'ConfigPair')
for config in CONFIGURATIONS:
for k, v in config.items():
config_list = ConfigList.objects.create(name=k)
for choice in v:
ConfigPair.objects.create(config_list=config_list, key=choice[0], value=choice[1], is_request_data_key=choice[2])


class Migration(migrations.Migration):

dependencies = [
('process_request', '0005_alter_user_email_alter_user_first_name_and_more'),
]

operations = [
migrations.RunPython(add_configurations),
]
38 changes: 19 additions & 19 deletions process_request/models.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
pass

archivist_group = [u"rac_archivists"]
donor_group = [u"rac_donors"]
researcher_group = [u"rac_researchers"]

AbstractUser._meta.get_field("email").blank = False
AbstractUser._meta.get_field("first_name").blank = False
AbstractUser._meta.get_field("last_name").blank = False
AbstractUser._meta.get_field("username").blank = False

@property
def full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
class ConfigList(models.Model):
name = models.CharField(max_length=255)

def __str__(self):
"""
Returns the full name and email of a user.
"""
return '{} <{}>'.format(self.full_name, self.email)
return self.name

def get_defaults(self, request_data={}):
list = {}
for pair in self.configpair_set.all():
value = request_data.get(pair.value) if pair.is_request_data_key else pair.value
list[pair.key] = value
return list


class ConfigPair(models.Model):
key = models.CharField(max_length=255, help_text="Key for the configuration.")
value = models.CharField(max_length=255, help_text="Value to assign to the configuration key.")
is_request_data_key = models.BooleanField(default=False, help_text="If checked, uses the value provided as a key to get a value from the request data.")
config_list = models.ForeignKey(ConfigList, on_delete=models.CASCADE)
57 changes: 16 additions & 41 deletions process_request/routines.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
get_preferred_format, get_resource_creators,
get_restricted_in_container, get_rights_info, get_size,
get_url, list_chunks)
from .models import ConfigList


class Processor(object):
Expand Down Expand Up @@ -186,28 +187,15 @@ class AeonRequester(object):
"""Creates transactions in Aeon by sending data to the Aeon API."""

def __init__(self):
self.request_defaults = {
"AeonForm": "EADRequest",
"DocumentType": "Default",
"GroupingIdentifier": "GroupingField",
"GroupingOption_EADNumber": "FirstValue",
"GroupingOption_ItemInfo1": "Concatenate",
"GroupingOption_ItemDate": "Concatenate",
"GroupingOption_ItemTitle": "FirstValue",
"GroupingOption_ItemAuthor": "FirstValue",
"GroupingOption_ItemSubtitle": "FirstValue",
"GroupingOption_ItemVolume": "FirstValue",
"GroupingOption_ItemIssue": "Concatenate",
"GroupingOption_ItemInfo2": "Concatenate",
"GroupingOption_CallNumber": "FirstValue",
"GroupingOption_ItemInfo3": "FirstValue",
"GroupingOption_ItemCitation": "FirstValue",
"GroupingOption_ItemNumber": "FirstValue",
"GroupingOption_Location": "FirstValue",
"GroupingOption_ItemInfo5": "FirstValue",
"UserReview": "No",
"SubmitButton": "Submit Request",
}
self.request_defaults = self.get_config_defaults("Request Defaults")

def get_config_defaults(self, list_name, request_data=None):
"""Returns dictionary of configuration defaults for a named list."""
try:
config_list = ConfigList.objects.get(name=list_name)
return config_list.get_defaults(request_data)
except ConfigList.DoesNotExist as e:
raise Exception(f"No ConfigList named {list_name} was found. Make sure a ConfigList matching that name is created in the Django Admin backend.") from e

def get_request_data(self, request_type, baseurl, **kwargs):
"""Gets object data from ArchivesSpace and formats it for reading rooom
Expand Down Expand Up @@ -248,16 +236,9 @@ def prepare_reading_room_request(self, items, request_data):
Returns:
data: Submission data for Aeon.
"""
reading_room_defaults = {
"WebRequestForm": "DefaultRequest",
"RequestType": "Loan",
"ScheduledDate": request_data.get("scheduledDate"),
"SpecialRequest": request_data.get("questions"),
"Site": request_data.get("site"),
}

request_data = self.parse_items(items)
return dict(**self.request_defaults, **reading_room_defaults, **request_data)
reading_room_defaults = self.get_config_defaults("Reading Room Defaults", request_data)
parsed_data = self.parse_items(items)
return dict(**self.request_defaults, **reading_room_defaults, **parsed_data)

def prepare_duplication_request(self, items, request_data):
"""Maps duplication request data to Aeon fields.
Expand All @@ -269,15 +250,9 @@ def prepare_duplication_request(self, items, request_data):
Returns:
data: Submission data for Aeon.
"""
duplication_defaults = {
"WebRequestForm": "PhotoduplicationRequest",
"RequestType": "Copy",
"Format": request_data.get("format"),
"SpecialRequest": request_data.get("questions"),
"SkipOrderEstimate": "Yes",
}
request_data = self.parse_items(items, request_data.get("description", ""))
return dict(**self.request_defaults, **duplication_defaults, **request_data)
duplication_defaults = self.get_config_defaults("Duplication Defaults", request_data)
parsed_data = self.parse_items(items, request_data.get("description", ""))
return dict(**self.request_defaults, **duplication_defaults, **parsed_data)

def parse_items(self, items, description=""):
"""Assigns item data to Aeon request fields.
Expand Down
12 changes: 0 additions & 12 deletions process_request/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
get_resource_creators, get_restricted_in_container,
get_rights_info, get_rights_status, get_rights_text,
get_size, indicator_to_integer, prepare_values)
from .models import User
from .routines import AeonRequester, Mailer, Processor
from .test_helpers import json_from_fixture, random_list, random_string
from .views import (DeliverDuplicationRequestView,
Expand All @@ -35,17 +34,6 @@
)


class TestUsers(TestCase):

def test_user(self):
user = User(
first_name="Patrick",
last_name="Galligan",
email="pgalligan@rockarch.org")
self.assertEqual(user.full_name, "Patrick Galligan")
self.assertEqual(str(user), "Patrick Galligan <pgalligan@rockarch.org>")


class TestHelpers(TestCase):

@aspace_vcr.use_cassette("aspace_request.json")
Expand Down
3 changes: 3 additions & 0 deletions request_broker/config.py.deploy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ DJANGO_DEBUG = ${DJANGO_DEBUG}
DJANGO_SECRET_KEY = "${DJANGO_SECRET_KEY}"
DJANGO_ALLOWED_HOSTS = ${DJANGO_ALLOWED_HOSTS}
DJANGO_CORS_ALLOWED_ORIGINS = ${DJANGO_CORS_ALLOWED_ORIGINS}
DJANGO_SUPERUSER_USERNAME = "${DJANGO_SUPERUSER_USERNAME}"
DJANGO_SUPERUSER_PASSWORD = "${DJANGO_SUPERUSER_PASSWORD}"
DJANGO_SUPERUSER_EMAIL = "${DJANGO_SUPERUSER_EMAIL}"
SQL_ENGINE = "${SQL_ENGINE}"
SQL_DATABASE = "${SQL_DATABASE}"
SQL_USER = "${SQL_USER}"
Expand Down
Loading