Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
""" Represents properties of Feature Classes derived from both defaults and developer-specified options within the Options inner-class. These properties drive the features of the spatial content managment system, such as CRUD operations, copy, sharing, etc.
"""
# Import down here to avoid circular reference
# call this here to ensure that permsissions get created #enable_sharing()
raise FeatureConfigurationError('Is not a subclass of \ madrona.features.models.Feature')
raise FeatureConfigurationError( 'Have not defined Options inner-class on registered feature \ class %s' % (name, ))
raise FeatureConfigurationError( "Feature class %s is not configured with a form class. \ To specify, add a `form` property to its Options inner-class." % (name,))
raise FeatureConfigurationError( "Feature class %s is configured with a form property that is \ not a string path." % (name,))
""" Path to FeatureForm used to edit this class. """
""" Name used in the url path to this feature as well as part of the Feature's uid """
""" Name specified or derived from the feature class name used in the user interface for representing this feature class. """
'features/form.html') """ Location of the template that should be used to render forms when editing or creating new instances of this feature class. """
""" Context to merge with default context items when rendering templates to create or modify features of this class. """
""" Context to merge with default context items when rendering templates to view information about instances of this feature class. """
""" Optional; URL to 16x16 icon to use in kmltree Use full URL or relative to MEDIA_URL """
""" Links associated with this class. """
self.links.extend(opts_links)
""" Enable copying features. Uses the feature class' copy() method. Defaults to True. """
# Add a copy method unless disabled 'madrona.features.views.copy', select='multiple single', edits_original=False))
# Add a multi-share generic link # TODO when the share_form view takes multiple instances # we can make sharing a generic link #self.links.insert(0, edit('Share', # 'madrona.features.views.share_form', # select='multiple single', # method='POST', # edits_original=True, #))
# Add a multi-delete generic link 'madrona.features.views.multi_delete', select='multiple single', method='DELETE', edits_original=True, confirm=confirm, ))
# Add a staticmap generic link 'madrona.staticmap.views.staticmap_link', select='multiple single', method='GET', ))
# Add a geojson generic link 'madrona.features.views.geojson_link', select='multiple single', method='GET', ))
""" valid child classes for the feature container """ raise FeatureConfigurationError("valid_children Option only \ for FeatureCollection classes" % m)
""" Required manipulators applied to user input geometries """ try: manip = get_class(m) except: raise FeatureConfigurationError("Error trying to import module %s" % m)
# Test that manipulator is compatible with this Feature Class geom_field = self._model.geometry_final._field.__class__.__name__ if geom_field not in manip.Options.supported_geom_fields: raise FeatureConfigurationError("%s does not support %s geometry types (only %r)" % (m, geom_field, manip.Options.supported_geom_fields))
#logger.debug("Added required manipulator %s" % m) self.manipulators.append(manip)
""" Optional manipulators that may be applied to user input geometries """ try: manip = get_class(m) except: raise FeatureConfigurationError("Error trying to import module %s" % m)
# Test that manipulator is compatible with this Feature Class geom_field = self._model.geometry_final._field.__class__.__name__ try: if geom_field not in manip.Options.supported_geom_fields: raise FeatureConfigurationError("%s does not support %s geometry types (only %r)" % (m, geom_field, manip.Options.supported_geom_fields)) except AttributeError: raise FeatureConfigurationError("%s is not set up properly; must have " "Options.supported_geom_fields list." % m)
#logger.debug("Added optional manipulator %s" % m) self.optional_manipulators.append(manip)
""" Enable kml visualization of features. Defaults to True. """ # Add a kml link by default 'madrona.features.views.kml', select='multiple single')) 'madrona.features.views.kmz', select='multiple single'))
""" Returns the template used to render this Feature Class' attributes """ # Grab a template specified in the Options object, or use the default template = getattr(self._options, 'show_template', '%s/show.html' % (self.slug, )) try: t = loader.get_template(template) except TemplateDoesNotExist: # If a template has not been created, use a stub that displays # some documentation on how to override the default template t = loader.get_template('features/show.html') return t
""" Returns the FeatureLink with the specified name """ try: link = [x for x in self.links if x.title == linkname][0] return link except: raise Exception("%r has no link named %s" % (self._model, linkname))
raise FeatureConfigurationError( "%r is not a properly configured FeatureCollection" % (self._model))
except: raise FeatureConfigurationError( "Error trying to import module %s" % vc)
raise FeatureConfigurationError( "%r is not a Feature; can't be a child" % vc)
""" It's not sufficient to look if this model is a valid_child of another FeatureCollection; that collection could contain other collections that contain this model.
Ex: Folder (only valid child is Array) Array (only valid child is MPA) Therefore, Folder is also a potential_parent of MPA """ potential_parents = [] direct_parents = [] collection_models = get_collection_models() for model in collection_models: opts = model.get_options() valid_children = opts.get_valid_children()
if self._model in valid_children: direct_parents.append(model) potential_parents.append(model)
for direct_parent in direct_parents: if direct_parent != self._model: potential_parents.extend(direct_parent.get_options().get_potential_parents())
return potential_parents
""" Returns the form class for this Feature Class. """ try: klass = get_class(self.form) except Exception, e: raise FeatureConfigurationError( "Feature class %s is not configured with a valid form class. \ Could not import %s.\n%s" % (self._model.__name__, self.form, e))
if not issubclass(klass, FeatureForm): raise FeatureConfigurationError( "Feature class %s's form is not a subclass of \ madrona.features.forms.FeatureForm." % (self._model.__name__, ))
return klass
""" Returns a json representation of this feature class configuration that can be used to specify client behavior """ placeholder = "%s_%d" % (self._model.model_uid(), 14) link_rels = { 'id': self._model.model_uid(), 'title': self.verbose_name, 'link-relations': { 'self': { 'uri-template': reverse("%s_resource" % (self.slug, ), args=[placeholder]).replace(placeholder, '{uid}'), 'title': settings.TITLES['self'], }, } }
if is_owner: lr = link_rels['link-relations'] lr['create'] = { 'uri-template': reverse("%s_create_form" % (self.slug, )) }
lr['edit'] = [ {'title': 'Edit', 'uri-template': reverse("%s_update_form" % (self.slug, ), args=[placeholder]).replace(placeholder, '{uid}') }, {'title': 'Share', 'uri-template': reverse("%s_share_form" % (self.slug, ), args=[placeholder]).replace(placeholder, '{uid}') }]
for link in self.links: if not link.generic and link.can_user_view(user, is_owner): if link.rel not in link_rels['link-relations'].keys(): if not (user.is_anonymous() and link.rel == 'edit'): link_rels['link-relations'][link.rel] = [] link_rels['link-relations'][link.rel].append(link.dict(user,is_owner))
if self._model in get_collection_models() and is_owner: link_rels['collection'] = { 'classes': [x.model_uid() for x in self.get_valid_children()], 'remove': { 'uri-template': reverse("%s_remove_features" % (self.slug, ), kwargs={'collection_uid':14,'uids':'xx'}).replace('14', '{collection_uid}').replace('xx','{uid+}') }, 'add': { 'uri-template': reverse("%s_add_features" % (self.slug, ), kwargs={'collection_uid':14,'uids':'xx'}).replace('14', '{collection_uid}').replace('xx','{uid+}') }
} return link_rels
return json.dumps(self.dict())
""" Returns the path to a form for creating new instances of this model """ return reverse('%s_create_form' % (self.slug, ))
""" Given a primary key, returns the path to a form for updating a Feature Class """ return reverse('%s_update_form' % (self.slug, ), args=['%s_%d' % (self._model.model_uid(), pk)])
""" Given a primary key, returns path to a form for sharing a Feature inst """ return reverse('%s_share_form' % (self.slug, ), args=['%s_%d' % (self._model.model_uid(), pk)])
""" Returns the primary url for a feature. This url supports GET, POST, and DELETE operations. """ return reverse('%s_resource' % (self.slug, ), args=['%s_%d' % (self._model.model_uid(), pk)])
type=None, slug=None, generic=False, models=None, extra_kwargs={}, confirm=False, edits_original=None, must_own=False, limit_to_groups=None):
"""Type of link - alternate, related, edit, or edit_form. """
View function handling requests to this link. """ except Exception as err: msg = 'Link "%s" configured with invalid path to view %s' % (title, view) msg += '\n%s\n' % str(err) if "cannot import" in str(err): msg += "(Possible cause: importing Features at the top level in views.py can cause" msg += " circular dependencies; Try to import Features within the view function)"
raise FeatureConfigurationError(msg)
""" Human-readable title for the link to be shown in the user interface. """
""" For rel=edit links, identifies whether a form should be requested or that url should just be POST'ed to. """
""" MIME type of this link, useful for alternate links. May in the future be used to automatically assign an icon in the dropdown Export menu. """
""" Part of this link's path. """
""" Determines whether this link accepts requests with single or multiple instances of a feature class. Valid values are "single", "multiple", "single multiple", and "multiple single". """
""" Extra keyword arguments to pass to the view. """
""" Whether this view can be applied to multiple feature classes. """
""" List of feature classes that a this view can be applied to, if it is generic. """
""" Confirmation message to show the user before POSTing to rel=edit link """
""" Set to false for editing links that create a copy of the original. This will allow users who do not own the instance(s) but can view them perform the action. """
""" Whether this link should be accessible to non-owners. Default link behavior is False; i.e. Link can be used for shared features as well as for user-owned features. If edits_original is true, this implies must_own = True as well. """
""" Allows you to specify groups (a list of group names) that should have access to the link. Default is None; i.e. All users have link access regardless of group membership """
# Make sure title isn't empty raise FeatureConfigurationError('Link title is empty') 'multiple single') # Check for valid 'select' kwarg raise FeatureConfigurationError( 'Link specified with invalid select option "%s"' % ( self.select, )) # Create slug from the title unless a custom slug is specified # Make sure the view has the right signature
""" Ensures view has a compatible signature to be able to hook into the features app url registration facilities
For single-select views must accept a second argument named instance For multiple-select views must accept a second argument named instances
Must also ensure that if the extra_kwargs option is specified, the view can handle them """ # Check for instance or instances arguments args = view.__code__.co_varnames if len(args) < 2 or args[1] != 'instance': raise FeatureConfigurationError('Link "%s" not configured \ with a valid view. View must take a second argument named instance.' % ( self.title, )) else: # select="multiple" or "multiple single" or "single multiple" raise FeatureConfigurationError('Link "%s" not configured \ with a valid view. View must take a second argument named instances.' % ( self.title, ))
""" Returns True/False depending on whether user can view the link. """ if self.limit_to_groups: # We rely on the auth Group model ensuring unique group names user_groupnames = [x.name for x in user.groups.all()] match = False for groupname in self.limit_to_groups: if groupname in user_groupnames: match = True break if not match: return False
if self.must_own and not is_owner: return False
return True
def url_name(self): """ Links are registered with named-urls. This function will return that name so that it can be used in calls to reverse(). """
def parent_slug(self): """ Returns either the slug of the only model this view applies to, or 'generic' """ return self.models[0].get_options().slug else:
"""Can be used to get the url for this link.
In the case of select=single links, just pass in a single instance. In the case of select=multiple links, pass in an array. """ if not isinstance(instances,tuple) and not isinstance(instances,list): instances = [instances] uids = ','.join([instance.uid for instance in instances]) return reverse(self.url_name, kwargs={'uids': uids})
return self.title
return str(self)
d = { 'rel': self.rel, 'title': self.title, 'select': self.select, 'uri-template': reverse(self.url_name, kwargs={'uids': 'idplaceholder'}).replace( 'idplaceholder', '{uid+}') } if self.rel == 'edit': d['method'] = self.method if len(self.models) > 1: d['models'] = [m.model_uid() for m in self.models] if self.confirm: d['confirm'] = self.confirm return d
return json.dumps(self.dict())
'select', 'must_own')
return create_link('related', *args, **kwargs)
if 'method' not in kwargs.keys(): kwargs['method'] = 'GET' return create_link('edit', *args, **kwargs)
registered_links.append(link)
workspace = { 'feature-classes': [], 'generic-links': [] } if not models: # Workspace doc gets ALL feature classes and registered links for model in registered_models: workspace['feature-classes'].append(model.get_options().dict(user, is_owner)) for link in registered_links: if link.generic and link.can_user_view(user, is_owner) \ and not (user.is_anonymous() and link.rel == 'edit'): workspace['generic-links'].append(link.dict(user, is_owner)) else: # Workspace doc only reflects specified feature class models for model in models: workspace['feature-classes'].append(model.get_options().dict(user, is_owner)) for link in registered_links: # See if the generic links are relavent to this list if link.generic and \ [i for i in args if i in link.models] and \ link.can_user_view(user, is_owner) and \ not (user.is_anonymous() and link.rel == 'edit'): workspace['generic-links'].append(link.dict(user, is_owner)) return json.dumps(workspace, indent=2)
""" Utility function returning models for registered and valid FeatureCollections """ except: pass
""" Utility function returning models for registered and valid Features excluding Collections """ from madrona.features.models import Feature, FeatureCollection registered_features = [] for model in registered_models: if issubclass(model,Feature) and not issubclass(model,FeatureCollection): registered_features.append(model) return registered_features
""" Returns a list of groups that user is member of and and group must have sharing permissions """ try: p = Permission.objects.get(codename='can_share_features') except Permission.DoesNotExist: return None
groups = user.groups.filter(permissions=p).distinct() return groups
""" Get a dict of groups and users that are currently sharing items with a given user If spatial_only is True, only models which inherit from the Feature class will be reflected here returns something like {'our_group': {'group': <Group our_group>, 'users': [<user1>, <user2>,...]}, ... } """ groups_sharing = {}
for model_class in registered_models: shared_objects = model_class.objects.shared_with_user(user) for group in user.groups.all(): # Unless overridden, public shares don't show up here if group.name in settings.SHARING_TO_PUBLIC_GROUPS and not include_public: continue # User has to be staff to see these if group.name in settings.SHARING_TO_STAFF_GROUPS and not user.is_staff: continue group_objects = shared_objects.filter(sharing_groups=group) user_list = [] for gobj in group_objects: if gobj.user not in user_list and gobj.user != user: user_list.append(gobj.user)
if len(user_list) > 0: if group.name in groups_sharing.keys(): for user in user_list: if user not in groups_sharing[group.name]['users']: groups_sharing[group.name]['users'].append(user) else: groups_sharing[group.name] = {'group':group, 'users': user_list} if len(groups_sharing.keys()) > 0: return groups_sharing else: return None
for model in registered_models: if model.model_uid() == muid: return model raise Exception("No model with model_uid == `%s`" % muid)
applabel, modelname, id = uid.split('_') id = int(id) model = get_model_by_uid("%s_%s" % (applabel,modelname)) instance = model.objects.get(pk=int(id)) return instance |