I haven't looked at the actual code at all yet, but:

We are using a custom metadata schema (ie, not any existing standard) for the GeoNode right now. I anticipate that organizations besides the World Bank may have other, (differently) customized metadata schemas that they want to use. This is probably going to be a huge pain for us in dealing with federation.

I wonder if we should look into other options before "baking in" our idea of a metadata schema to the Layer model. I haven't done this yet either but I was thinking about looking at the user/profile separation in django.contrib.auth for inspiration.

--
David Winslow
OpenGeo - http://opengeo.org/

On 07/06/2010 10:06 AM, Ariel Nunez wrote:
*
*Here is a short update on the work I am doing on the metadata branch, feedback is more than welcome.*

Metadata Refactor
=================

The following metadata related tickets are being addressed in my personal metadata branch on github [0]:

#345 Metadata summary on Layer Information page assigned
#486 Add metadata records to GeoNetwork on sync and upload
#492 Pre-fill metadata form with values from GeoNetwork records
#498 Integrate metadata form with Django models
#500 Add metadata fields to Django Layer model
#501 Enable syncing of Django Layer model records to GeoNetwork metadata records
#564 Persist metadata upon submission

The big changes happened in the ``geonode/maps`` application, mostly in models.py following the "fat models, skinny controllers" mantra.

models.py
---------

Let's start with an explanation (and some snippets of code) about the new entities structure. The first thing, is that the Layer/Contact model now matches one to one the World Bank's specification (which is based on ISO 19115:2003). Including validation of optional/required fields.

Contact model
~~~~~~~~~~~~~

It represents a contact information object and can either be tied or not to a geonode website user. When a contact object does not have a user linked, then it's just an anonymous contact object, when a contact object is attached to a user, it becomes that user's ``profile information``. This renders unnecessary the ``geonode_profile`` application that existed in the repo.

class Contact(models.Model):
    user = models.ForeignKey(User, blank=True, null=True)
# the specification says that either name or organization should be provided
    name = models.CharField('Individual Name', blank=True, null=True)
organization = models.CharField('Organization Name', blank=True, null=True)
    # position, voice, fax, delivery, city, area, zipcode, country, email

Layer model
~~~~~~~~~~~

The layer model existed in the repo and contained mainly structures to interact with GeoServer (workspace, store, typename) and a few with GeoNetwork (uuid), this model has been augmented with the fields defined in the spec (title, date, edition, constraints) and a ``ManyToMany`` field pointing at contacts. The spec defines two basic roles: ``Point Of Contact`` and ``Metadata Author``, and the Layer model provides accessors to those two roles. However, by using a ``ManyToMany`` relation, we can add as many contacts as we want to a given layer, using the clearly defined ``Role`` field to specify the type of relation.

class Layer(models.Model):
    # objects,workspace,store,storeType,name,uuid,typename

    contacts = models.ManyToManyField(Contact, through='ContactRole')
    poc = property(_get_poc, _set_poc)
    metadata_author = property(_get_metadata_author, _set_metadata_author)

    # title,date,date_type,edition,abstract,purpose,maintenance_frequency
    # keywords,keywords_region,constraints_use,constraints_other,
    # spatial_representation_type,language,topic_category,
    # temporal_extent_start,temporal_extent_end,geographic_bounding_box,
    # supplemental_information,distribution_url,distribution_description,
    # data_quality_statement = models.TextField(blank=True, null=True)

Role and ContactRole models
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The role model ties a given role with a sets of permissions, that way we can detail the type of access each role should have to a given resource, to plant the seed for a future ``per-object role based permission system``. The ContactRole model is used as an intermediary table on the ManyToMany relationship between layers and contacts. More information about this structure can be found in Django Docs [1]

ROLE_VALUES = [
    'datasetProvider',
    'custodian',
    'owner',
    'user',
    'distributor',
    'originator',
    'pointOfContact',
    'principalInvestigator',
    'processor',
    'publisher',
    'author'
]

class Role(models.Model):
    value = models.CharField('Role', choices= ROLE_VALUES, unique=True)
    permissions = models.ManyToManyField(Permission)

class ContactRole(models.Model):
    contact = models.ForeignKey(Contact)
    layer = models.ForeignKey(Layer)
    role = models.ForeignKey(Role)

Layer.slurp
~~~~~~~~~~~

The slurp method now gets more information from GeoNetwork (bbox, keywords, distribution url) and sets the ``poc`` and ``metadata_author`` attributes.

Layer.save_to_geonetwork and Layer.save_to_geoserver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

These ugly named methods serialize the relevant information in the layer object and push it to the relevant server (GeoServer or GeoNetwork). It is hooked to the post_save signal of the Layer model.

Layer.delete_from_geoserver and Layer.delete_from_geonetwork
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Similar to the ones described above, it is hooked to the pre_delete signal of the Layer model.

forms.py
--------

Since all the information is now baked in the models, the ContactForm and MetadataForm are no longer needed, they are automatically created using the form_from_model function Django provides.

views.py
--------

A lot of the initialization code from GeoNode and custom form handling was removed, the view that serves "describe layer" is now very slim and saves both the layer form as well as the poc and metadata_author ones.

settings.py
-----------

'registration' and 'profiles' are now part of ``geonode/settings.py``, the profile module is now set to be ``maps.Contact`` to allow both anonymous contacts and users with contact information as explained above.

AUTH_PROFILE_MODULE = 'maps.Contact'

Running the branch
==================

I am going to copy/paste/tweak David's notes about running a branch from a different repo below:

   # add 'ingenieroariel' as an alias for my github fork.
   # the '-f' means "go ahead and fetch from that repo now"
git remote add ingenieroariel git://github.com/ingenieroariel/geonode.git <http://github.com/ingenieroariel/geonode.git> -f

   # create a local branch (checkout -b) that you can work on that tracks
   # my github repo (eg, uses it for pull and merge)
   git checkout ingenieroariel/metadata -b metadata --track

I plan to create a patch against the current master to make it easier to review the code changes, I'll do that today after doing the merge with last week's geonode development.

Ariel.

[0] http://github.com/ingenieroariel/geonode/tree/metadata
[1] http://docs.djangoproject.com/en/1.2/topics/db/models/#intermediary-manytomany*


Reply via email to