PREMIS Event Service Documentation

Contents:

Overview

For a general overview of the PREMIS Event Service, please see the project README.rst file (included below for convenience).


PREMIS Event Service

https://travis-ci.org/unt-libraries/django-premis-event-service.svg?branch=master

PREMIS Event Service is a Django application for managing PREMIS Events in a structured, centralized, and searchable manner.

Purpose

The purpose of this application is to provide a straightforward way to send PREMIS-formatted events to a central location to be stored and retrieved. In this fashion, it can serve as an event logger for any number of services that happen to wish to use it. PREMIS is chosen as the underlying format for events due to its widespread use in the digital libraries world.

Dependencies

  • Python 3
  • Django 1.11
  • lxml (requires libxml2-dev to be installed on your system)
  • pipenv

Documentation

Documentation, including installation instructions, can be viewed online at:

http://premis-event-service.readthedocs.org/

The documentation is also browsable locally from within the docs directory of this repository. You can read the source files in plain text from the docs/source directory, or generate your own local copy of the HTML files by doing the following:

  1. Make sure Sphinx is installed (pip install sphinx)
  2. cd docs
  3. make html
  4. Open index.html (generated in docs/build/html)

License

See LICENSE.

Acknowledgements

The Premis Event Service was developed at the UNT Libraries and has been worked on by a number of developers over the years including

  • Kurt Nordstrom
  • Joey Liechty
  • Lauren Ko
  • Stephen Eisenhauer
  • Mark Phillips
  • Damon Kelley
  • Reed Underwood
  • Andromeda Yelton (MIT)
  • Madhulika Bayyavarapu

If you have questions about the project feel free to contact Mark Phillips at mark.phillips@unt.edu

Developing

There are two (supported) ways to develop the PREMIS event service Django app. One is natively using an SQLite backend. The other is using a MySQL backend for storage inside a Docker container.

Developing Natively Using SQLite
Clone the repository
$ git clone https://github.com/unt-libraries/django-premis-event-service.git # check the repo for the latest official release if you don't want the development version at HEAD on the master branch
$ cd django-premis-event-service
Install the requirements using pipenv
$ pipenv --python 3.7 # (to create the virtualenv)
$ pipenv install --dev
$ pipenv shell # (to enter the virtualenv)
$ exit # (to leave the virtualenv)

If you need to generate a requirements.txt file, you can do so with pipenv lock -r > requirements.txt.

Run the tests using tox
$ tox
Apply the migrations
$ python manage.py migrate
Start the development server
$ python manage.py runserver 9999

This will start the development server listening locally on port 9999. You may want to change the port number, passed as the first argument to the runserver command.

View the web UI in a browser

Navigate to http://localhost:9999/event/ (or whatever port you chose) to see the UI of the app.

Developing Using Docker and MySQL as a Backend
Install Docker

On Debian-derived Linux distros, you can use apt-get to install. If you’re on a different OS, check the Docker site for instructions.

Install Docker Compose
$ pip install docker-compose

Alternatively, you may want to install docker-compose using your system’s package manager.

Clone the repository
$ git clone https://github.com/unt-libraries/django-premis-event-service.git # check the repo for the latest official release if you don't want the development version at HEAD on the master branch
$ cd django-premis-event-service
Starting the app
# start the app
$ docker-compose up -d db app

# If you make changes to the models, create and apply a migration
$ docker-compose run manage makemigrations
$ docker-compose run manage migrate

# optional: add a superuser in order to login to the admin interface
$ docker-compose run manage createsuperuser
View the web UI in a browser

Navigate to http://localhost:8000/event/ to see the UI of the app. The port can be changed by editing the docker-compose.yml file.

The code is in a volume that is shared between your workstation and the app container, which means any edits you make on your workstation will also be reflected in the Docker container. No need to rebuild the container to pick up changes in the code.

However, if the Pipfile.lock changes, it is important that you rebuild the app container for those packages to be installed. This is something that could happen when switching between feature branches; when installing new dependencies during development; or when pulling updates from the remote.

# stop the app
$ docker-compose stop

# remove the app container
$ docker-compose rm app

# rebuild the app container
$ docker-compose build app # under some circumstances, you may need to use the --no-cache switch, e.g. upstream changes to packages the app requires

# start the app
$ docker-compose up -d db app
Viewing the logs
$ docker-compose logs -f
Running the Tests

To run the tests via Tox, use this command.

$ docker-compose run --rm test

Technical Overview

Events

A standard PREMIS event encoded as XML looks something like the following:

<?xml version="1.0"?>
<premis:event xmlns:premis="info:lc/xmlns/premis-v2">
    <premis:eventIdentifier>
        <premis:eventIdentifierType>
            http://purl.org/net/untl/vocabularies/identifier-qualifiers/#UUID
        </premis:eventIdentifierType>
        <premis:eventIdentifierValue>
            9e42cbd3cc3b4dfc888522036bbc4491
        </premis:eventIdentifierValue>
    </premis:eventIdentifier>
    <premis:eventType>
        http://purl.org/net/untl/vocabularies/preservationEvents/#fixityCheck
    </premis:eventType>
    <premis:eventDateTime>2017-05-13T14:14:55Z</premis:eventDateTime>
    <premis:eventDetail>
        There is no muse of philosophy, nor is there one of translation.
    </premis:eventDetail>
    <premis:eventOutcomeInformation>
        <premis:eventOutcome>
            http://purl.org/net/untl/vocabularies/eventOutcomes/#success
        </premis:eventOutcome>
        <premis:eventOutcomeDetail>
            <premis:eventOutcomeDetailNote>
                Total time for verification: 0:00:01.839590
            </premis:eventOutcomeDetailNote>
        </premis:eventOutcomeDetail>
    </premis:eventOutcomeInformation>
    <premis:linkingAgentIdentifier>
        <premis:linkingAgentIdentifierType>
            http://purl.org/net/untl/vocabularies/identifier-qualifiers/#URL
        </premis:linkingAgentIdentifierType>
        <premis:linkingAgentIdentifierValue>
            http://localhost:8787/agent/codaMigrationVerification
        </premis:linkingAgentIdentifierValue>
    </premis:linkingAgentIdentifier>
    <premis:linkingObjectIdentifier>
        <premis:linkingObjectIdentifierType>
            http://purl.org/net/untl/vocabularies/identifier-qualifiers/#ARK
        </premis:linkingObjectIdentifierType>
        <premis:linkingObjectIdentifierValue>
            ark:/67531/coda10kx
        </premis:linkingObjectIdentifierValue>
        <premis:linkingObjectRole/>
    </premis:linkingObjectIdentifier>
</premis:event>

This is a lot at first glance, but the pieces are more or less logical. The relevant things that a given PREMIS event record keeps track of are the following:

  • Event Identifier - This is a unique identifier assigned to every event when it is entered into the system. This is what is used to reference given event.
  • Event Type - This is an arbitrary value to categorize the kind of event we’re logging. Examples might include fixity checking, virus scanning or replication.
  • Event Time - This is a timestamp for when the event itself occurred.
  • Event Added - This is a timestamp for when the event was logged.
  • Event Outcome - This is the simple description of the outcome. Usually something like “pass” or “fail”.
  • Outcome Details - A more detailed record of the outcome. Perhaps output from a secondary program might go here.
  • Agent - This is the identifier for the agent that initiated the event. An agent can be anything, from a person, to an institution, to a program. The PREMIS event service will also allow you to track agent entries as well.
  • Linked Objects - These are identifiers for relevant objects that the event is associated with. If your system uses object identifiers, you could put those identifiers here when an event pertains to them.

It is important to note that most of the values that you use in a given PREMIS event record are arbitrary. You decide on your own values and vocabularies, and use what makes sense to you. It doesn’t enforce any sort of constraints as far as that goes. The service is responsible for indexing all PREMIS events sent to it and providing retrieval for them. Basic retrieval is on a per-identifier basis, but it is plausible to assume that you may wish to request events based on date added, agent used, event type, event outcome, or a combination of these factors.

Agents

The PREMIS metadata specification defines a separate spec for agents that looks like the following:

<?xml version="1.0"?>
<premis:agent xmlns:premis="info:lc/xmlns/premis-v2">
    <premis:agentIdentifier>
        <premis:agentIdentifierType>
            http://purl.org/net/untl/vocabularies/identifier-qualifiers/#URL
        </premis:agentIdentifierType>
        <premis:agentIdentifierValue>
            http://localhost:8787/agent/codaMigrationVerification
        </premis:agentIdentifierValue>
    </premis:agentIdentifier>
    <premis:agentName>
        codaMigrationVerification
    </premis:agentName>
    <premis:agentType>softw</premis:agentType>
</premis:agent>

As you can see from the above example, the agent’s identifier above corresponds with the agent in the event example. You are able to create and register agents through the administrative panel on the PREMIS service; see the Administration section to learn how.

Note that there is no schematic relationship between Agent objects and Event objects in the application’s database tables. Events may be linked to any Agent identifier and are not limited in any way to Agent items created in administrative interface.

Installation

The project’s README.rst file contains some basic installation instructions. We’ll elaborate a bit in this section.

Dependencies

  • Python 3.7.x
  • Django 1.11
  • libxml2-dev libxslt-dev
  • Django Admin - django.contrib.admin

Important security warning

This application does not attempt to authenticate requests or differentiate between clients in any way – even for write and edit operations via the API. Do not simply expose the application to the public in your server configuration. Instead, use a network firewall to whitelist the server to authorized clients, or use a web server configuration directive (such as Apache’s <LimitExcept GET>) to set up who is allowed to POST/PUT/DELETE events.

Install

  1. Install the package.

    $ pip install git+https://github.com/unt-libraries/django-premis-event-service
    $ # check https://github.com/unt-libraries/django-premis-event-service/releases for the latest release
    
  2. Add premis_event_service to your INSTALLED_APPS. Be sure to add django.contrib.admin if it is not already present.

    INSTALLED_APPS = (
        'django.contrib.admin',
        # ...
        'premis_event_service',
    )
    
  1. Include the URLs.

    urlpatterns = [
        url(r'', include('premis_event_service.urls'))
        # ...
        url(r'^admin/', include(admin.site.urls)),
    ]
    
  2. Migrate the database.

    $ python manage.py migrate
    
  3. Continue to Administration to begin setting up Agents.

Configuration

All configuration related to the PREMIS Event Service takes place inside your project’s settings.py file.

Note: Make sure you only make changes in your project’s settings.py, not the settings.py file inside the premis_event_service app directory.

Mandatory Configuration

  1. Update your INSTALLED_APPS setting as follows:

    INSTALLED_APPS = (
        ...
        'django.contrib.humanize',
        'premis_event_service',
    )
    
  2. Make sure you have a TEMPLATE_CONTEXT_PROCESSORS setting defined containing at least the entries shown below:

    TEMPLATE_CONTEXT_PROCESSORS = (
        'django.contrib.auth.context_processors.auth',
        'django.core.context_processors.debug',
        'django.core.context_processors.i18n',
        'django.core.context_processors.media',
        'django.core.context_processors.request',
    )
    
  3. In your MIDDLEWARE_CLASSES setting, remove or comment out the CsrfViewMiddleware entry:

    MIDDLEWARE_CLASSES = (
        ...
        #'django.middleware.csrf.CsrfViewMiddleware',
        ...
    )
    
  4. Add a MAINTENANCE_MSG setting at the bottom of the file:

    MAINTENANCE_MSG = ''  # Message to show during maintenance
    

Customizing the Controlled Vocabulary

Deciding on Controlled Vocabulary Design

The Premis Event Service was designed to us a wide variety of or identifiers for values within PREMIS Event Objects. That being said there are some best practices that can be suggested to new a implementer.

It is advantageous for someone implementing the Premis Event Service to make use of existing controlled vocabularies whenever possible for some of the concepts that are used throughout the application. For example the Library of Congress has added a number of Preservation Vocabulary entries to its Authorities and Vocabularies Service. Starting with these identifiers for concepts such as “Fixity Check”, “Replication”, “Ingestion”, or “Migration” is a suggestion unless there is a reason to deviate from these in a local implementation.

Additional concepts that are not covered by the Library of Congress Authorities and Vocabularies Service are those for the outcome of an event, for example “Success” and “Failure”. The Premis Event Service has placeholders set aside for these values that utilize the controlled vocabularies at the University of North Texas: http://purl.org/NET/untl/vocabularies/

The Premis Event Service will work without fully fleshed out controlled vocabularies, and the authors have worked to give examples with reasonable values which can be added to or modified to meet local needs.

Configuring a Custom Controlled Vocabulary

The Event Service makes no attempt to validate values given to it against any set of allowed values; it is up to your policies and integrations to enforce consistency across the events you store.

However, you can change the choices that are shown in the “Search” interface by adding some statements like these to your settings.py file:

EVENT_OUTCOME_CHOICES = (
    ('', 'None'),
    ('http://purl.org/net/untl/vocabularies/eventOutcomes/#success', 'Success'),
    ('http://purl.org/net/untl/vocabularies/eventOutcomes/#failure', 'Failure'),
)
EVENT_TYPE_CHOICES = (
    ('', 'None'),
    ('http://id.loc.gov/vocabulary/preservation/eventType/fix', 'Fixity Check'),
    ('http://id.loc.gov/vocabulary/preservation/eventType/rep', 'Replication'),
    ('http://id.loc.gov/vocabulary/preservation/eventType/ing', 'Ingestion'),
    ('http://id.loc.gov/vocabulary/preservation/eventType/mig', 'Migration'),
)

Administration

This section outlines the initial work needed after installation in order to prepare your Event Service for use.

Manage User Accounts

To create an admin account, run python manage.py createsuperuser and follow the prompts.

To manage or create other user accounts, do the following:

  1. Visit the Django admin interface (http://[host]/admin/) in a web browser.
  2. Log in using your superuser account.
  3. Click Users. This takes you to the list of Users.
  4. Click the Add user button near the top-right corner of the page.
  5. Fill and submit the form.

Keep in mind that any account needing the ability to also administer user accounts using the admin interface will need to be given “superuser” status.

Create an Agent

Every event stored in the Event Service must be associated with an Agent. Agents merely represent entities that produce events. In many cases these are software processes (e.g. a web application or a script), but an agent can also be a person, an institution, or anything else.

To create a new agent (or to manage existing ones), do the following:

  1. Visit the Django admin interface (http://[host]/admin/) in a web browser.
  2. Log in using your superuser account (if you haven’t already).
  3. Click Agents. This takes you to the list of Agents, which will be empty at first.
  4. Click the Add agent button near the top-right corner of the page.
  5. Fill and submit the form.

Create as many agents as you have a need for.

Using the Event Service

There are two ways of using the PREMIS Event Service:

  • using the web interface to view and manage events by hand
  • using the APIs to create or query events from other software workflows

This document will cover how to use the web interface and admin site. For information about the APIs, refer to the next section (API).

Events

Browse all Events

URL: http://[host]/event/

Human readable HTML listing of events.

View a single Event

URL: http://[host]/event/[id]/

Human readable HTML listing of a single event. Contains links to other formats/representations of the event, such as PREMIS XML.

Search for Events

URL: http://[host]/event/search/

Web interface for searching events. Events can be filtered by outcome, type, start/end dates, or Linked Object ID.

Agents

Browse all Agents

URL: http://[host]/agent/

Human readable HTML listing of agents.

View a single Agent

URL: http://[host]/agent/[id]/

Human readable HTML listing of a single agent. Contains links to other formats/representations of the agent, such as PREMIS XML.

API

The bulk of event creation using the Event Service will probably take place via software as opposed to by hand. This section explains the AtomPub API (Application Programming Interface) used for interacting with the Event Service from your custom applications and scripts.

Introduction

The PREMIS Event Service uses REST to handle the message passing between client and server. To better provide a standard set of conventions for this, we have elected to follow the AtomPub protocol for POSTing and GETing events from the system. The base unit for AtomPub is the Atom “entry” tag, which is what gets sent back and forth. The actual PREMIS metadata is embedded in the entry’s “content” tag. There is a lot more to AtomPub than that, but for the purpose of this document, it is helpful to just view the Atom entry as an “envelope” for the PREMIS XML.

PREMIS

The PREMIS Event Service makes every effort to conform to the PREMIS v.2 specification. Versions 2.* of the spec are not backwards compatible with versions before the 2.0 milestone.

A Note on Dates

Unless otherwise noted, all datetimes mentioned below must be formatted as xsDateTime compliant strings. The output of the datetime.isoformat method in Python is compatible.

API URL Structure

APIs for communicating with the Event Service programmatically are located under the /APP/ URL tree:

/APP/

AtomPub service document

The service document is an XML file that explains, to an AtomPub aware client, what services and URLs exist at this site. It’s an integral part of the AtomPub specification, and allows for things like auto-discovery.

/APP/event/

AtomPub feed for event entries

Accepts parameters:

  • start - This is the index of the first record that you want…it starts indexing at 1.
  • count - This is the number of records that you want returned.
  • start_date - This is a date (or partial date ) in ISO8601 format that indicates the earliest record that you want.
  • end_date - This is a date that indicates the latest record that you want.
  • type - This is a string identifying a type identifier (or partial identifier) that you want to filter events by
  • outcome - This is a string identifying an outcome identifier (partial matching is supported)
  • link_object_id - This is an identifier that specifies that we want events pertaining to a particular object
  • orderdir - This defaults to ‘ascending’. Specifying ‘descending’ will return the records in reverse order.
  • orderby - This parameter specifies what field to order the records by. The valid fields are currently: event_date_time (default), event_identifier, event_type, event_outcome

For the human-viewable feeds, the parameters are the same, except, instead of using a ‘start’ parameter, it uses a ‘page’ parameter, because of the way it paginates the output (see below).

Also serves as a POST point for new entries.

Issuing a ‘GET’ to this URL will return an Atom feed of entries that represent PREMIS events.

This is the basic form of aggregation that AtomPub uses. Built into the Atom feed are tags thatallow for easy pagination, so crawlers will be able to process received data in manageable chunks. Additionally, this URL will accept a number of GET arguments, in order to filter the results that are returned.

This is also the endpoint for adding new events to the system, in which case a PREMIS Event is sent within an Atom entry in the form of an HTTP POST request.

/APP/event/<id>/

Permalink for Atom entry for a given event

This is the authoritative link for a given PREMIS Event entry, based upon the unique identifier that each event is assigned when it is logged into the system. It returns the event record contained within an Atom entry.

/APP/agent/

AtomPub feed for agent entries

Issuing a ‘GET’ request here returns an AtomPub feed of PREMIS Agent records. Because there will be far less agents than events in a given system, it is not known that we’ll build search logic into this URL.

According to the AtomPub spec, this would be where we’d allow adding new Agents via POST, but because there are likely so few times that we’d need to add Agents, we would just as well leave this to be done through the admin interface.

/APP/agent/<id>/

Permalink for Atom entry for a given agent

The authoritative link for a given PREMIS Agent entry, based on the agent’s unique id. Next are the URLs designed for human consumption.

Example

The example below is a somewhat plausible one, using a fixity check event during a migration as a scenario:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0"?>
<premis:event xmlns:premis="info:lc/xmlns/premis-v2">
    <premis:eventIdentifier>
        <premis:eventIdentifierType>
            http://purl.org/net/untl/vocabularies/identifier-qualifiers/#UUID
        </premis:eventIdentifierType>
        <premis:eventIdentifierValue>
            9e42cbd3cc3b4dfc888522036bbc4491
        </premis:eventIdentifierValue>
    </premis:eventIdentifier>
    <premis:eventType>
        http://purl.org/net/untl/vocabularies/preservationEvents/#fixityCheck
    </premis:eventType>
    <premis:eventDateTime>2017-05-13T14:14:55Z</premis:eventDateTime>
    <premis:eventDetail>
        There is no muse of philosophy, nor is there one of translation.
    </premis:eventDetail>
    <premis:eventOutcomeInformation>
        <premis:eventOutcome>
            http://purl.org/net/untl/vocabularies/eventOutcomes/#success
        </premis:eventOutcome>
        <premis:eventOutcomeDetail>
            <premis:eventOutcomeDetailNote>
                Total time for verification: 0:00:01.839590
            </premis:eventOutcomeDetailNote>
        </premis:eventOutcomeDetail>
    </premis:eventOutcomeInformation>
    <premis:linkingAgentIdentifier>
        <premis:linkingAgentIdentifierType>
            http://purl.org/net/untl/vocabularies/identifier-qualifiers/#URL
        </premis:linkingAgentIdentifierType>
        <premis:linkingAgentIdentifierValue>
            http://localhost:8787/agent/codaMigrationVerification
        </premis:linkingAgentIdentifierValue>
    </premis:linkingAgentIdentifier>
    <premis:linkingObjectIdentifier>
        <premis:linkingObjectIdentifierType>
            http://purl.org/net/untl/vocabularies/identifier-qualifiers/#ARK
        </premis:linkingObjectIdentifierType>
        <premis:linkingObjectIdentifierValue>
            ark:/67531/coda10kx
        </premis:linkingObjectIdentifierValue>
        <premis:linkingObjectRole/>
    </premis:linkingObjectIdentifier>
</premis:event>

As you can see, the values chosen for the tags in the PREMIS event XML are arbitrary, and it is the responsibility of the user to select something that makes sense in the context of their organization. One thing to note is that the values for the eventIdentifierType and eventIdentifierValue will be overwritten, because the Event Service manages the event identifiers, and assigns new ones upon ingest.

Now, in order to send the event to the Event Service, it must be wrapped in an Atom entry, so the following Atom wrapper XML tree is created:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<entry xmlns="http://www.w3.org/2005/Atom">
    <title>9e42cbd3cc3b4dfc888522036bbc4491</title>
    <id>http://localhost:9999/APP/event/9e42cbd3cc3b4dfc888522036bbc4491/</id>
    <updated>2017-05-13T14:14:55Z</updated>
    <author>
        <name>Object Verification Script</name>
    </author>
    <content type="application/xml">
        <premis:event xmlns:premis="info:lc/xmlns/premis-v2">
            ...
        </premis:event>
    </content>
</entry>

(With the previously-generated PREMIS XML going inside of the “content” tag.)

Now that the entry is generated and wrapped in a valid Atom document, it is ready for upload. In order to do this, we POST the Atom XML to the /APP/event/ URL.

When the Event Service receives the POST, it reads the content and parses the XML. If it finds a valid XML PREMIS event document, it will assign the event an identifier, index the values and save them, and then generate a return document, also wrapped in an Atom entry. It will look something like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0"?>
    <entry xmlns="http://www.w3.org/2005/Atom">
    <title>9e42cbd3cc3b4dfc888522036bbc4491</title>
    <id>http://localhost:8000/APP/event/9e42cbd3cc3b4dfc888522036bbc4492/</id>
    <updated>2017-03-27T09:15:31.382106-05:00</updated>
    <content type="application/xml">
        <premis:event xmlns:premis="info:lc/xmlns/premis-v2">
            <premis:eventIdentifier>
                <premis:eventIdentifierType>
                    http://purl.org/net/untl/vocabularies/identifier-qualifiers/#UUID
                </premis:eventIdentifierType>
                <premis:eventIdentifierValue>
                    9e42cbd3cc3b4dfc888522036bbc4491
                </premis:eventIdentifierValue>
            </premis:eventIdentifier>
            <premis:eventType>
                http://purl.org/net/untl/vocabularies/preservationEvents/#fixityCheck
            </premis:eventType>
            <premis:eventDateTime>
                2017-05-13T09:14:55-05:00
            </premis:eventDateTime>
            <premis:eventDetail>
                There is no muse of philosophy, nor is there one of translation.
            </premis:eventDetail>
            <premis:eventOutcomeInformation>
                <premis:eventOutcome>
                    http://purl.org/net/untl/vocabularies/eventOutcomes/#success
                </premis:eventOutcome>
                <premis:eventOutcomeDetail>
                    <premis:eventOutcomeDetailNote>
                        Total time for verification: 0:00:01.839590
                    </premis:eventOutcomeDetailNote>
                </premis:eventOutcomeDetail>
            </premis:eventOutcomeInformation>
            <premis:linkingAgentIdentifier>
                <premis:linkingAgentIdentifierType>
                    http://purl.org/net/untl/vocabularies/identifier-qualifiers/#URL
                </premis:linkingAgentIdentifierType>
                <premis:linkingAgentIdentifierValue>
                    http://localhost:8787/agent/codaMigrationVerification
                </premis:linkingAgentIdentifierValue>
            </premis:linkingAgentIdentifier>
            <premis:linkingObjectIdentifier>
                <premis:linkingObjectIdentifierType>
                    http://purl.org/net/untl/vocabularies/identifier-qualifiers/#ARK
                </premis:linkingObjectIdentifierType>
                <premis:linkingObjectIdentifierValue>
                    ark:/67531/coda10kx
                </premis:linkingObjectIdentifierValue>
                <premis:linkingObjectRole/>
            </premis:linkingObjectIdentifier>
        </premis:event>
    </content>
</entry>

If the POST is successful, the updated record will be returned, along with a status of 201. If the status is something else, there was an error, and the event cannot be considered to have been reliably recorded.

Later, when we (or, perhaps, another script) wish to review the event to find out what went wrong with the file validation, we would access it by sending an HTTP GET request to /APP/event/9e42cbd3cc3b4dfc888522036bbc4491, which would return an Atom entry containing the final event record, which we could then analyze and use for whatever purposes desired.

Development

Here, you will find some information helpful if you plan on developing upon or making changes to the Event Service source code itself.

Project Structure

The PREMIS Event Service is structured as a common Python project, providing a Python package named premis_event_service which is a Django app:

premis_event_service/
├── admin.py          ## Customizes the Django admin interface
├── forms.py          ## Form definitions and validation code
├── __init__.py       ## Makes this directory a Python package
├── migrations        ## Django database migrations
│   ├── 0001_initial.py
│   ├── 0002_add_event_ordinal.py
│   └── __init__.py
├── models.py         ## Data models, using Django ORM
├── presentation.py   ## Business logic
├── settings.py       ## App-specific settings
├── templates
│   └── premis_event_service
│       ├── agent.html
│       ├── base.html
│       ├── event.html
│       ├── recent_event_list.html
│       └── search.html
├── urls.py           ## App-specific url patterns/routes
└── views.py          ## Route handlers which generate human- and machine-readable views

If you’re not sure where to look for something, urls.py is usually the best place to start. There you’ll find a list of every URL pattern handled by the application, along with its corresponding view (found in views.py) and arguments.

Models

Models define the data objects Django keeps in its database. The PREMIS Event Service defines these three:

  • Event - Represents an event.
  • Agent - Represents an agent you’ve defined using the Django admin interface.
  • LinkObject - Contains an identifier for an object in your preservation workflow. Exists for the purpose of relating multiple events that pertain to the same object.

See premis_event_service/models.py for the full definitions to these models.

Views

View are functions (or sometimes classes) that Django calls upon to generate the result of a request. Usually this just means rendering some HTML from a template and serving it, but sometimes this involves form processing and API interactions as well. Django decides which view to run based on what’s defined in urls.py.

See premis_event_service/views.py for the full source code to all the views provided by the Event Service.