class User(db.Document, UserMixin): email = db.EmailField() password = db.StringField(max_length=255) active = db.BooleanField(default=True) confirmed_at = db.DateTimeField() current_login_at = db.DateTimeField() last_login_at = db.DateTimeField() current_login_ip = db.StringField(max_length=45) last_login_ip = db.StringField(max_length=45) login_count = db.IntField(default=0) roles = db.ListField(db.ReferenceField(Role, reverse_delete_rule=db.PULL), default=[]) deployment = db.ReferenceField(Deployment) meta = { 'indexes': [ ['deployment'], ['deployment', 'email'], ['deployment', 'email', 'password'] ] } def __unicode__(self): return self.email or u''
class FormField(db.EmbeddedDocument): '''A :class:`mongoengine.EmbeddedDocument` used in storing the Checklist/Critical Incident form questions in a :class:`core.documents.Form` model. Each :class:`core.documents.FormField` has attributes for specifying various behaviour for the form field. :attr:`analysis_type` which specifies the sort of data analysis to be performed on the field and is defined by the values stored in :attr:`ANALYSIS_TYPES` :attr:`represents_boolean` which is either True for a FormField that accepts only one value (e.g. Critical Incident form fields) :attr:`options` which is a dictionary that has keys representing field option values and values representing the option description. (e.g. {'1': 'Yes'}) :attr:`allows_multiple_values` which is a boolean field specifying whether the field will accept multiple values as correct responses :attr:`min_value` which specifies the minimum accepted value and :attr:`max_value` for specifying the maximum valid value. :attr:`description` stores the textual description of the field. :attr:`name` is the question code used to identify the field (e.g. AA)''' ANALYSIS_TYPES = (('N/A', _('Not Applicable')), ('PROCESS', _('Process Analysis')), ('RESULT', _('Results Analysis'))) name = FormFieldNameField(required=True) description = db.StringField(required=True) max_value = db.IntField(default=9999) min_value = db.IntField(default=0) allows_multiple_values = db.BooleanField(default=False) is_comment_field = db.BooleanField(default=False) options = db.DictField() represents_boolean = db.BooleanField(default=False) analysis_type = db.StringField(choices=ANALYSIS_TYPES, default='N/A')
class LocationType(db.Document): '''Stores the type describing the administrative level of a Location :attr ancestors_ref: This stores a list references to ancestor loction types as documented in http://docs.mongodb.org/manual/tutorial/model-tree-structures/''' name = db.StringField() ancestors_ref = db.ListField(db.ReferenceField( 'LocationType', reverse_delete_rule=db.PULL)) ancestor_count = db.IntField() is_administrative = db.BooleanField(default=False) is_political = db.BooleanField(default=False) has_registered_voters = db.BooleanField(db_field='has_rv', default=False) has_political_code = db.BooleanField(db_field='has_pc', default=False) has_other_code = db.BooleanField(db_field='has_oc', default=False) metafields = db.ListField(db.StringField()) slug = db.StringField() deployment = db.ReferenceField(Deployment) meta = { 'indexes': [ ['deployment'] ] } @classmethod def get_root_for_event(cls, event): return cls.objects.get(events=event, __raw__={'ancestors_ref': []}) @classmethod def root(cls): return cls.objects.get(__raw__={'ancestors_ref': []}) def clean(self): if not self.slug: self.slug = slugify_unicode(self.name).lower() self.ancestor_count = len(self.ancestors_ref) return super(LocationType, self).clean() @property def children(self): """Returns a list of descendants sorted by the length of the `attr`ancestors_ref. """ temp = LocationType.objects(ancestors_ref=self) return sorted(temp, None, lambda x: len(x.ancestors_ref)) def __unicode__(self): return self.name or u''
class Location(db.DynamicDocument): '''A store for Locations''' name = db.StringField() code = db.StringField() political_code = db.StringField(db_field='pcode') other_code = db.StringField(db_field='ocode') location_type = db.StringField() coords = db.GeoPointField() registered_voters = db.LongField(db_field='rv', default=0) ancestor_count = db.IntField(default=0) ancestors_ref = db.ListField(db.ReferenceField( 'Location', reverse_delete_rule=db.PULL)) samples = db.ListField(db.ReferenceField( 'Sample', reverse_delete_rule=db.PULL)) events = db.ListField(db.ReferenceField( Event, reverse_delete_rule=db.PULL)) deployment = db.ReferenceField(Deployment) meta = { 'indexes': [ ['ancestors_ref'], ['ancestors_ref', 'location_type'], ['samples'], ['events'], ['location_type'], ['name'], ['name', 'location_type'], ['code'], ['political_code'], ['events', 'code'], ['deployment'], ['deployment', 'events'] ], 'queryset_class': LocationQuerySet } @classmethod def get_root_for_event(cls, event): return cls.objects.get(events=event, __raw__={'ancestors_ref': []}) @classmethod def root(cls): return cls.objects.get(__raw__={'ancestors_ref': []}) @property def children(self): return Location.objects( ancestors_ref__all=self.ancestors_ref + [self], ancestors_ref__size=len(self.ancestors_ref) + 1) @property def descendants(self): return Location.objects(ancestors_ref=self) @property def ancestors(self): return self.ancestors_ref def ancestor(self, location_type, include_self=True): try: return filter(lambda l: l.location_type == location_type, self.ancestors_ref + [self] if include_self else self.ancestors_ref)[0] except IndexError: pass def __unicode__(self): return self.name or u'' def save(self, *args, **kwargs): from . import LocationsService cache.delete_memoized(LocationsService.registered_voters_map) return super(Location, self).save(*args, **kwargs) def _update_ancestor_count(self, save=False): if self.ancestor_count != len(self.ancestors_ref): self.ancestor_count = len(self.ancestors_ref) if save: self.update(set__ancestor_count=self.ancestor_count) def clean(self): self._update_ancestor_count()
class Participant(db.DynamicDocument): '''Storage for participant contact information''' GENDER = (('F', _('Female')), ('M', _('Male')), ('', _('Unspecified'))) participant_id = db.StringField() name = db.StringField() role = db.ReferenceField('ParticipantRole') partner = db.ReferenceField('ParticipantPartner') location = db.ReferenceField('Location') location_name_path = db.DictField() supervisor = db.ReferenceField('Participant') gender = db.StringField(choices=GENDER, default='') groups = db.ListField( db.ReferenceField(ParticipantGroup, reverse_delete_rule=db.PULL)) email = db.EmailField() phones = db.ListField(db.EmbeddedDocumentField(PhoneContact)) message_count = db.IntField(default=0) accurate_message_count = db.IntField(default=0) event = db.ReferenceField(Event) deployment = db.ReferenceField(Deployment) completion_rating = db.FloatField(default=1) device_id = db.StringField() password = db.StringField() meta = { 'indexes': [['participant_id'], ['device_id'], ['location'], ['phones.number'], ['event'], ['name'], ['role'], ['partner'], ['groups'], ['deployment'], ['deployment', 'event']], 'queryset_class': ParticipantQuerySet } def __unicode__(self): return self.name or u'' def clean(self): # unlike for submissions, this always gets called, because # participants are 'mobile' - they can be moved from one location # to another. we want this to reflect that. self.location_name_path = compute_location_path(self.location) if self.gender not in map(lambda op: op[0], self.GENDER): self.gender = '' def get_phone(self): if self.phones: return self.phones[0].number else: return None def set_phone(self, value): # TODO: blind overwrite is silly. find a way to ensure the number # doesn't already exist if not self.phones: self.phones.append(PhoneContact(number=value, verified=True)) else: self.phones[0].number = value self.phones[0].verified = True self.save() self.reload() phone = property(get_phone, set_phone) @property def last_seen_phone(self): if self.phones: phones = sorted(self.phones, key=lambda p: p.last_seen if p.last_seen else datetime.fromtimestamp(0)) phones.reverse() return phones[0].number else: return None