1 from django.contrib.gis.db import models
2 from django.contrib.auth.models import User, Group
3 from django.conf import settings
4 from django.contrib.contenttypes.models import ContentType
5 from django.contrib.contenttypes import generic
6 from django.http import HttpResponse
7 from django.utils.html import escape
8 from madrona.features.managers import ShareableGeoManager
9 from madrona.features.forms import FeatureForm
10 from madrona.features import get_model_options
11 from madrona.common.utils import asKml, clean_geometry, ensure_clean
12 from madrona.common.utils import get_logger, get_class, enable_sharing
13 from madrona.manipulators.manipulators import manipulatorsDict, NullManipulator
14 import re
15 import mapnik
16
17 logger = get_logger()
20 """Model used for representing user-generated features
21
22 ====================== ==============================================
23 Attribute Description
24 ====================== ==============================================
25 ``user`` Creator
26
27 ``name`` Name of the object
28
29 ``date_created`` When it was created
30
31 ``date_modified`` When it was last updated.
32 ====================== ==============================================
33 """
34 user = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_related")
35 name = models.CharField(verbose_name="Name", max_length="255")
36 date_created = models.DateTimeField(auto_now_add=True,
37 verbose_name="Date Created")
38 date_modified = models.DateTimeField(auto_now=True,
39 verbose_name="Date Modified")
40 sharing_groups = models.ManyToManyField(Group,editable=False,blank=True,
41 null=True,verbose_name="Share with the following groups",
42 related_name="%(app_label)s_%(class)s_related")
43 content_type = models.ForeignKey(ContentType, blank=True, null=True,
44 related_name="%(app_label)s_%(class)s_related")
45 object_id = models.PositiveIntegerField(blank=True,null=True)
46 collection = generic.GenericForeignKey('content_type', 'object_id')
47
48 objects = ShareableGeoManager()
49
51 return u"%s_%s" % (self.model_uid(), self.pk)
52
54 return u"%s_%s" % (self.model_uid(), self.pk)
55
58
59 '''
60 Note on keyword args rerun and form: These are extracted from kwargs so that they will not cause an unexpected
61 keyword argument error during call to super.save. (They are used in the Analysis model save method, but become
62 superfluous here.)
63 '''
64 - def save(self, rerun=True, form=None, *args, **kwargs):
68
69 @models.permalink
74
75 @classmethod
77 """
78 Returns model class Options object
79 """
80 return get_model_options(klass.__name__)
81
82 @classmethod
84 """
85 Specifies the CSS for representing features in kmltree, specifically the icon
86 Works one of two ways:
87 1. Use the icon_url Option and this default css() classmethod
88 2. Override the css() classmethod for more complex cases
89 """
90 url = klass.get_options().icon_url
91 if url:
92 if not url.startswith("/") and not url.startswith("http://"):
93 url = settings.MEDIA_URL + url
94 return """ li.%s > .icon {
95 background: url("%s") no-repeat scroll 0 0 transparent ! important;
96 display:inline ! important;
97 }
98
99 div.%s > .goog-menuitem-content {
100 background: url("%s") no-repeat scroll 0 0 transparent !important;
101 display: block !important;
102 padding-left: 22px;
103 position: relative;
104 left: -22px;
105 height: 16px;
106 } """ % (klass.model_uid(), url, klass.model_uid(), url)
107
108 @property
111
112 @classmethod
114 """
115 class method providing the uid for the model class.
116 """
117 ct = ContentType.objects.get_for_model(klass)
118 return "%s_%s" % (ct.app_label, ct.model)
119
120 @property
122 """
123 For caching. This string represents a hash of all
124 attributes that may influence reporting results.
125 i.e. if this property changes, reports for the feature get rerun.
126 """
127 important = "%s%s" % (self.date_modified, self.uid)
128 return important.__hash__()
129
130 @property
132 """
133 Unique identifier for this feature.
134 """
135 if not self.pk:
136 raise Exception(
137 'Trying to get uid for feature class that is not yet saved!')
138 return "%s_%s" % (self.model_uid(), self.pk, )
139
140 @property
142 """
143 A safety valve for kmlapp...
144 If one feature's .kml property fails,
145 it won't bring down the entire request.
146 This property is never to be overridden!
147 """
148 try:
149 return self.kml
150 except Exception as e:
151 try:
152 logger.error("%s .kml property is failing: \n%s\n" % (self.uid,e.message))
153 except:
154
155 print ".kml is failing on something"
156
157 return """
158 <Placemark id="%s">
159 <visibility>0</visibility>
160 <name>%s (KML Error)</name>
161 <description>Error Details ... %s ... If the problem persists, please contact us.</description>
162 </Placemark>
163 """ % (self.uid, self.name, e.message)
164
174
184
186 """
187 Share this feature with the specified group/groups.
188 Owner must be a member of the group/groups.
189 Group must have 'can_share' permissions else an Exception is raised
190 """
191 if not append:
192
193
194 self.sharing_groups.clear()
195
196 if groups is None or groups == []:
197
198 return True
199
200 if isinstance(groups,Group):
201
202 groups = [groups]
203
204 for group in groups:
205 assert isinstance(group, Group)
206
207 assert group in self.user.groups.all()
208 try:
209 gp = group.permissions.get(codename='can_share_features')
210 except:
211 raise Exception("The group you are trying to share with "
212 "does not have can_share permission")
213
214 self.sharing_groups.add(group)
215
216 self.save(rerun=False)
217 return True
218
220 """
221 Is this feauture viewable by the specified user?
222 Either needs to own it or have it shared with them.
223 returns : Viewable(boolean), HttpResponse
224 """
225
226 if user.is_anonymous() or not user.is_authenticated():
227 try:
228 obj = self.__class__.objects.shared_with_user(user).get(pk=self.pk)
229 return True, HttpResponse("Object shared with public, viewable by anonymous user", status=202)
230 except self.__class__.DoesNotExist:
231
232 return False, HttpResponse('You must be logged in', status=401)
233
234
235 if self.user == user:
236 return True, HttpResponse("Object owned by user",status=202)
237
238
239 try:
240
241
242 obj = self.__class__.objects.shared_with_user(user).get(pk=self.pk)
243 return True, HttpResponse("Object shared with user", status=202)
244 except self.__class__.DoesNotExist:
245 return False, HttpResponse("Access denied", status=403)
246
247 return False, HttpResponse("Server Error in feature.is_viewable()", status=500)
248
249 - def copy(self, user=None):
250 """
251 Returns a copy of this feature, setting the user to the specified
252 owner. Copies many-to-many relations
253 """
254
255
256
257
258 the_feature = self
259
260
261 m2m = {}
262 for f in the_feature._meta.many_to_many:
263 m2m[f.name] = the_feature.__getattribute__(f.name).all()
264
265
266
267
268
269
270 the_feature.pk = None
271 the_feature.id = None
272 the_feature.save(rerun=False)
273
274 the_feature.name = the_feature.name + " (copy)"
275
276
277 for fname in m2m.keys():
278 for obj in m2m[fname]:
279 the_feature.__getattribute__(fname).add(obj)
280
281
282 the_feature.user = user
283
284
285 the_feature.sharing_groups.clear()
286 the_feature.remove_from_collection()
287
288 the_feature.save(rerun=False)
289 return the_feature
290
292 """
293 Abstract Model used for representing user-generated geometry features.
294 Inherits from Feature and adds geometry-related methods/properties
295 common to all geometry types.
296
297 ====================== ==============================================
298 Attribute Description
299 ====================== ==============================================
300 ``user`` Creator
301
302 ``name`` Name of the object
303
304 ``date_created`` When it was created
305
306 ``date_modified`` When it was last updated.
307
308 ``manipulators`` List of manipulators to be applied when geom
309 is saved.
310 ====================== ==============================================
311 """
312 manipulators = models.TextField(verbose_name="Manipulator List", null=True,
313 blank=True, help_text='csv list of manipulators to be applied')
314
317
318 - def save(self, *args, **kwargs):
323
324 @property
330
331 @classmethod
333 """
334 Mapnik style object containing rules for symbolizing features in staticmap
335 """
336 return None
337 style = mapnik.Style()
338 return style
339
340 @property
342 """
343 Fully-styled KML placemark representation of the feature.
344 The Feature's kml property MUST
345 - return a string containing a valid KML placemark element
346 - the placemark must have id= [the feature's uid]
347 - if it references any style URLs, the corresponding Style element(s)
348 must be provided by the feature's .kml_style property
349 """
350 return """
351 <Placemark id="%s">
352 <visibility>1</visibility>
353 <name>%s</name>
354 <styleUrl>#%s-default</styleUrl>
355 <ExtendedData>
356 <Data name="name"><value>%s</value></Data>
357 <Data name="user"><value>%s</value></Data>
358 <Data name="modified"><value>%s</value></Data>
359 </ExtendedData>
360 %s
361 </Placemark>
362 """ % (self.uid, self.name, self.model_uid(),
363 self.name, self.user, self.date_modified,
364 self.geom_kml)
365
366 @property
368 """
369 Must return a string with one or more KML Style elements
370 whose id's may be referenced by relative URL
371 from within the feature's .kml string
372 In any given KML document, each *unique* kml_style string will get included
373 so don't worry if you have 10 million features with "blah-default" style...
374 only one will appear in the final document and all the placemarks can refer
375 to it. BEST TO TREAT THIS LIKE A CLASS METHOD - no instance specific vars.
376 """
377
378 return """
379 <Style id="%s-default">
380 <IconStyle>
381 <color>ffffffff</color>
382 <colorMode>normal</colorMode>
383 <scale>0.9</scale>
384 <Icon> <href>http://maps.google.com/mapfiles/kml/paddle/wht-blank.png</href> </Icon>
385 </IconStyle>
386 <BalloonStyle>
387 <bgColor>ffeeeeee</bgColor>
388 <text> <![CDATA[
389 <font color="#1A3752"><strong>$[name]</strong></font><br />
390 <font size=1>Created by $[user] on $[modified]</font>
391 ]]> </text>
392 </BalloonStyle>
393 <LabelStyle>
394 <color>ffffffff</color>
395 <scale>0.8</scale>
396 </LabelStyle>
397 <PolyStyle>
398 <color>778B1A55</color>
399 </PolyStyle>
400 <LineStyle>
401 <color>ffffffff</color>
402 </LineStyle>
403 </Style>
404 """ % (self.model_uid())
405
406 @property
408 """
409 This method contains all the logic to determine which manipulators get applied to a feature
410
411 If self.manipulators doesnt exist or is null or blank,
412 apply the required manipulators (or the NullManipulator if none are required)
413
414 If there is a self.manipulators string and there are optional manipulators contained in it,
415 apply the required manipulators PLUS the specified optional manipulators
416 """
417 active = []
418 try:
419 manipulator_list = self.manipulators.split(',')
420 if len(manipulator_list) == 1 and manipulator_list[0] == '':
421
422 manipulator_list = []
423 except AttributeError:
424 manipulator_list = []
425
426 required = self.options.manipulators
427 try:
428 optional = self.options.optional_manipulators
429 except AttributeError:
430 optional = []
431
432
433 active.extend(required)
434
435 if len(manipulator_list) < 1:
436 if not required or len(required) < 1:
437 manipulator_list = ['NullManipulator']
438 else:
439 return active
440
441
442 for manipulator in manipulator_list:
443 manipClass = manipulatorsDict.get(manipulator)
444 if manipClass and (manipClass in optional or manipClass == NullManipulator):
445 active.append(manipClass)
446
447 return active
448
468
470 """
471 Model used for representing user-generated polygon features. Inherits from SpatialFeature.
472
473 ====================== ==============================================
474 Attribute Description
475 ====================== ==============================================
476 ``user`` Creator
477
478 ``name`` Name of the object
479
480 ``date_created`` When it was created
481
482 ``date_modified`` When it was last updated.
483
484 ``manipulators`` List of manipulators to be applied when geom
485 is saved.
486
487 ``geometry_original`` Original geometry as input by the user.
488
489 ``geometry_final`` Geometry after manipulators are applied.
490 ====================== ==============================================
491 """
492 geometry_orig = models.PolygonField(srid=settings.GEOMETRY_DB_SRID,
493 null=True, blank=True, verbose_name="Original Polygon Geometry")
494 geometry_final = models.PolygonField(srid=settings.GEOMETRY_DB_SRID,
495 null=True, blank=True, verbose_name="Final Polygon Geometry")
496
497 @property
504
505 @classmethod
507 polygon_style = mapnik.Style()
508 ps = mapnik.PolygonSymbolizer(mapnik.Color('#ffffff'))
509 ps.fill_opacity = 0.5
510 ls = mapnik.LineSymbolizer(mapnik.Color('#555555'),0.75)
511 ls.stroke_opacity = 0.5
512 r = mapnik.Rule()
513 r.symbols.append(ps)
514 r.symbols.append(ls)
515 polygon_style.rules.append(r)
516 return polygon_style
517
520
522 """
523 Model used for representing user-generated linestring features. Inherits from SpatialFeature.
524
525 ====================== ==============================================
526 Attribute Description
527 ====================== ==============================================
528 ``user`` Creator
529
530 ``name`` Name of the object
531
532 ``date_created`` When it was created
533
534 ``date_modified`` When it was last updated.
535
536 ``manipulators`` List of manipulators to be applied when geom
537 is saved.
538
539 ``geometry_original`` Original geometry as input by the user.
540
541 ``geometry_final`` Geometry after manipulators are applied.
542 ====================== ==============================================
543 """
544 geometry_orig = models.LineStringField(srid=settings.GEOMETRY_DB_SRID,
545 null=True, blank=True, verbose_name="Original LineString Geometry")
546 geometry_final = models.LineStringField(srid=settings.GEOMETRY_DB_SRID,
547 null=True, blank=True, verbose_name="Final LineString Geometry")
548
549 @classmethod
551 line_style = mapnik.Style()
552 ls = mapnik.LineSymbolizer(mapnik.Color('#444444'),1.5)
553 ls.stroke_opacity = 0.5
554 r = mapnik.Rule()
555 r.symbols.append(ls)
556 line_style.rules.append(r)
557 return line_style
558
561
563 """
564 Model used for representing user-generated point features. Inherits from SpatialFeature.
565
566 ====================== ==============================================
567 Attribute Description
568 ====================== ==============================================
569 ``user`` Creator
570
571 ``name`` Name of the object
572
573 ``date_created`` When it was created
574
575 ``date_modified`` When it was last updated.
576
577 ``manipulators`` List of manipulators to be applied when geom
578 is saved.
579
580 ``geometry_original`` Original geometry as input by the user.
581
582 ``geometry_final`` Geometry after manipulators are applied.
583 ====================== ==============================================
584 """
585 geometry_orig = models.PointField(srid=settings.GEOMETRY_DB_SRID,
586 null=True, blank=True, verbose_name="Original Point Geometry")
587 geometry_final = models.PointField(srid=settings.GEOMETRY_DB_SRID,
588 null=True, blank=True, verbose_name="Final Point Geometry")
589
590 @classmethod
592 point_style = mapnik.Style()
593 r = mapnik.Rule()
594 r.symbols.append(mapnik.PointSymbolizer())
595 point_style.rules.append(r)
596 return point_style
597
600
602 """
603 A Folder/Collection of Features
604 """
607
611
613 """Removes a specified Feature from the Collection"""
614 if f.collection == self:
615 f.remove_from_collection()
616 self.save()
617 else:
618 raise Exception('Feature `%s` is not in Collection `%s`' % (f.name, self.name))
619
620 - def save(self, rerun=True, *args, **kwargs):
622
623 @property
625 features = self.feature_set()
626 kmls = [x.kml for x in features]
627 return """
628 <Folder id="%s">
629 <name>%s</name>
630 <visibility>0</visibility>
631 <open>0</open>
632 %s
633 </Folder>
634 """ % (self.uid, self.name, ''.join(kmls))
635
636 @property
638 return """
639 <Style id="%(model_uid)s-default">
640 </Style>
641 """ % {'model_uid': self.model_uid()}
642
643 @property
646
647 - def feature_set(self, recurse=False, feature_classes=None):
648 """
649 Returns a list of Features belonging to the Collection
650 Optionally recurse into all child containers
651 or limit/filter for a list of feature classes
652 """
653 feature_set = []
654
655
656 if issubclass(feature_classes.__class__, Feature):
657 feature_classes = [feature_classes]
658
659 for model_class in self.get_options().get_valid_children():
660 if recurse and issubclass(model_class, FeatureCollection):
661 collections = model_class.objects.filter(
662 content_type=ContentType.objects.get_for_model(self),
663 object_id=self.pk
664 )
665 for collection in collections:
666 feature_list = collection.feature_set(recurse, feature_classes)
667 if len(feature_list) > 0:
668 feature_set.extend(feature_list)
669
670 if feature_classes and model_class not in feature_classes:
671 continue
672
673 feature_list = list(
674 model_class.objects.filter(
675 content_type=ContentType.objects.get_for_model(self),
676 object_id=self.pk
677 )
678 )
679
680 if len(feature_list) > 0:
681 feature_set.extend(feature_list)
682
683 return feature_set
684
685 - def copy(self, user=None):
686 """
687 Returns a copy of this feature collection, setting the user to the specified
688 owner. Recursively copies all children.
689 """
690 original_feature_set = self.feature_set(recurse=False)
691
692 the_collection = self
693
694
695 m2m = {}
696 for f in the_collection._meta.many_to_many:
697 m2m[f.name] = the_collection.__getattribute__(f.name).all()
698
699
700
701 the_collection.pk = None
702 the_collection.id = None
703 the_collection.save()
704
705 the_collection.name = the_collection.name + " (copy)"
706
707
708 for fname in m2m.keys():
709 for obj in m2m[fname]:
710 the_collection.__getattribute__(fname).add(obj)
711
712
713 the_collection.user = user
714
715
716 the_collection.sharing_groups.clear()
717 the_collection.remove_from_collection()
718 the_collection.save()
719
720 for child in original_feature_set:
721 new_child = child.copy(user)
722 new_child.add_to_collection(the_collection)
723
724 the_collection.save()
725 return the_collection
726
727 - def delete(self, *args, **kwargs):
734