We already know which database objects we are going to need to store the uploaded shapefiles:
Shapefile object will represent a single uploaded shapefileAttribute objects, giving the name, data type, and other information about each attribute within the shapefileFeature objects which hold the geometry for each of the shapefile's featuresAttributeValue objects which hold the value for each of the feature's attributesLet's look at each of these in more detail and think about exactly what information will need to be stored in each object.
When we import a shapefile, there are a few things we need to remember:
Feature object holds the geometry.When we export a shapefile, it has to have the same attributes as the original imported file. Because of this, we have to remember the shapefile's attributes. This is what the Attribute object does. We will need to remember the following information for each attribute:
All of this information comes directly from the shapefile's layer definition.
Each feature in the imported shapefile will need to be stored in the database. Because PostGIS (and GeoDjango) uses different field types for different types of geometries, we need to define separate fields for each geometry type. Because of this, the Feature object will need to store the following information:
Isn't something missing?
If you've been paying attention, you've probably noticed that some of the geometry types are missing. What about Polygons and LineStrings? Because of the way data is stored in a shapefile, it is impossible to know in advance whether a shapefile holds Polygons or MultiPolygons and, similarly, whether it holds LineStrings or MultiLineStrings (or Points or MultiPoints). The shapefile's internal structure makes no distinction between these geometry types. Because of this, a shapefile may claim to store Polygons when it really contains MultiPolygons and may make similar claims for LineString geometries. For more information, check out http://code.djangoproject.com/ticket/7218.
To work around this limitation, we store all Polygons as MultiPolygons, all LineStrings as MultiLineStrings, and all Points as MultiPoints. This is why we don't need Polygon, Point, or LineString fields in the Feature object.
The
AttributeValue object holds the value for each of the feature's attributes. This object is quite straightforward, storing the following information:
For simplicity, we'll store all attribute values as strings.
Now that we know what information we want to store in our database, it's easy to define our various model objects. To do this, edit the models.py file in the shapeEditor.shared directory, and make sure it looks like this:
from django.contrib.gis.db import models
class Shapefile(models.Model):
filename = models.CharField(max_length=255)
srs_wkt = models.CharField(max_length=255)
geom_type = models.CharField(max_length=50)
class Attribute(models.Model):
shapefile = models.ForeignKey(Shapefile)
name = models.CharField(max_length=255)
type = models.IntegerField()
width = models.IntegerField()
precision = models.IntegerField()
class Feature(models.Model):
shapefile = models.ForeignKey(Shapefile)
geom_point = models.PointField(srid=4326,
blank=True, null=True)
geom_multipoint = \
models.MultiPointField(srid=4326,
blank=True, null=True)
geom_multilinestring = \
models.MultiLineStringField(srid=4326,
blank=True, null=True)
geom_multipolygon = \
models.MultiPolygonField(srid=4326,
blank=True, null=True)
geom_geometrycollection = \
models.GeometryCollectionField(srid=4326,
blank=True,
null=True)
objects = models.GeoManager()
class AttributeValue(models.Model):
feature = models.ForeignKey(Feature)
attribute = models.ForeignKey(Attribute)
value = models.CharField(max_length=255,
blank=True, null=True)There are a few things to be aware of here:
from...import statement at the top has changed. We're importing the GeoDjango models rather than the standard Django ones.models.CharField objects to represent character data and models.IntegerField objects to represent integer values. Django provides a whole raft of field types for you to use. GeoDjango also adds its own field types to store geometry fields, as you can see from the definition of the Feature object.models.ForeignKey object.Feature object will store geometry data, we want to allow GeoDjango to perform spatial queries using this data. To enable this, we define a GeoManager() instance for the Feature class.geom_XXX fields in the Feature object) have both blank=True and null=True. These are actually quite distinct: blank=True means that the admin interface allows the user to leave the field blank, while null=True tells the database that these fields can be set to NULL in the database. For the Feature object, we'll need both so that we don't get validation errors when entering geometries via the admin interface.That's all we need to do (for now) to define our database models. After you've made these changes, save the file, cd into the topmost project directory, and type the following:
python manage.py makemigrations shared
As we saw in the previous chapter, the makemigrations command creates a database migration file for the given application. With this migration in place, we can now tell Django to create a database structure to match the contents of our database models:
python manage.py migrate
This command tells Django to check the models and create new database tables as required. You should now have a spatial database set up with the various database tables you have defined. Let's take a closer look at this database by typing the following:
psql shapeeditor
When you see the Postgres command prompt, type \d and press Return. You should see a list of all the database tables that have been created:
List of relations Schema | Name | Type | Owner --------+-----------------------------------+----------+------- public | auth_group | table | shapeeditor public | auth_group_id_seq | sequence | shapeeditor public | auth_group_permissions | table | shapeeditor public | auth_group_permissions_id_seq | sequence | shapeeditor public | auth_permission | table | shapeeditor public | auth_permission_id_seq | sequence | shapeeditor public | auth_user | table | shapeeditor public | auth_user_groups | table | shapeeditor public | auth_user_groups_id_seq | sequence | shapeeditor public | auth_user_id_seq | sequence | shapeeditor public | auth_user_user_permissions | table | shapeeditor public | auth_user_user_permissions_id_seq | sequence | shapeeditor public | django_admin_log | table | shapeeditor public | django_admin_log_id_seq | sequence | shapeeditor public | django_content_type | table | shapeeditor public | django_content_type_id_seq | sequence | shapeeditor public | django_migrations | table | shapeeditor public | django_migrations_id_seq | sequence | shapeeditor public | django_session | table | shapeeditor public | geography_columns | view | postgres public | geometry_columns | view | postgres public | raster_columns | view | postgres public | raster_overviews | view | postgres public | shared_attribute | table | shapeeditor public | shared_attribute_id_seq | sequence | shapeeditor public | shared_attributevalue | table | shapeeditor public | shared_attributevalue_id_seq | sequence | shapeeditor public | shared_feature | table | shapeeditor public | shared_feature_id_seq | sequence | shapeeditor public | shared_shapefile | table | shapeeditor public | shared_shapefile_id_seq | sequence | shapeeditor public | spatial_ref_sys | table | postgres (32 rows)
To make sure that each application's database tables are unique, Django adds the application name to the start of the table name. This means that the table names for the models we have created within the shared application are actually called shared_shapefile, shared_feature, and so on. We'll be working with these database tables directly later on, when we want to use Mapnik to generate maps using the imported Shapefile data.
When you have finished with the Postgres command-line client, type \q and press Return to quit psql.
Now that our database has been set up, let's create a "superuser" account so that we can access it. You can do this by entering the following command:
python manage.py createsuperuser
You'll be prompted to enter the username, e-mail address and password for a new superuser; we'll need this for the next section, where we explore GeoDjango's built-in admin interface.