class NotifiableEvent(AuditableModel): owner = models.ForeignKey(User, verbose_name=strings.NOTIFIABLE_EVENT_OWNER) notification_type = models.PositiveIntegerField( verbose_name=strings.NOTIFIABLE_EVENT_NOTIFICATION_TYPE, choices=constants.NOTIFICATION_TYPE_CHOICES) info = models.TextField(verbose_name=strings.NOTIFIABLE_EVENT_INFO) processed = models.BooleanField( verbose_name=strings.NOTIFIABLE_EVENT_PROCESSED, default=False) processed_at = models.DateTimeField( verbose_name=strings.NOTIFIABLE_EVENT_PROCESSED_AT, blank=True, null=True) class Meta: verbose_name = strings.NOTIFIABLE_EVENT_VERBOSE_NAME verbose_name_plural = strings.NOTIFIABLE_EVENT_VERBOSE_NAME_PLURAL def register(self): from notifications.tasks import process_event # Sending task to Celery backend process_event.apply_async(kwargs={'event_id': self.pk}, serializer="json") def mark_as_processed(self): self.processed = True self.processed_at = datetime.datetime.now() self.save() def create_notification(self, user): notification = Notification() notification.notified_user = user notification.info = self.info notification.notification_type = self.notification_type notification.save() return notification def make_info_dict(self): return json.loads(self.info) @staticmethod def register_event_circuit_created(owner, circuit, timestamp=None): metadata = { 'author': circuit.author.id, 'circuit': circuit.id, } nt = constants.NOTIFICATION_TYPE_CHOICES.CIRCUIT_CREATED event = NotifiableEvent( owner=owner, notification_type=nt, ) if timestamp is None: timestamp = datetime.datetime.now() metadata['timestamp'] = unicode(timestamp) event.info = render_as_json(metadata) event.save() event.register() return event @staticmethod def register_event_circuit_favorited(owner, circuit, timestamp=None): metadata = { 'user': owner.id, 'circuit': circuit.id, } nt = constants.NOTIFICATION_TYPE_CHOICES.CIRCUIT_FAVORITED event = NotifiableEvent(owner=owner, notification_type=nt) if timestamp is None: timestamp = datetime.datetime.now() metadata['timestamp'] = unicode(timestamp) event.info = render_as_json(metadata) event.save() event.register() return event @staticmethod def register_event_circuit_remixed(owner, remixed_circuit, original_circuit, timestamp=None): metadata = { 'user': owner.pk, 'remixed_circuit': remixed_circuit.id, 'original_circuit': original_circuit.id, } nt = constants.NOTIFICATION_TYPE_CHOICES.CIRCUIT_REMIXED event = NotifiableEvent( owner=owner, notification_type=nt, ) if timestamp is None: timestamp = datetime.datetime.now() metadata['timestamp'] = unicode(timestamp) event.info = render_as_json(metadata) event.save() event.register() return event @staticmethod def register_event_circuit_updated(owner, circuit, timestamp=None): metadata = { 'circuit': circuit.id, } nt = constants.NOTIFICATION_TYPE_CHOICES.CIRCUIT_UPDATED event = NotifiableEvent( owner=owner, notification_type=nt, ) if timestamp is None: timestamp = datetime.datetime.now() metadata['timestamp'] = unicode(timestamp) event.info = render_as_json(metadata) event.save() event.register() return event @staticmethod def register_event_user_followed(owner, followed, timestamp=None): metadata = { 'follower': owner.id, 'followed': followed.id, } nt = constants.NOTIFICATION_TYPE_CHOICES.USER_FOLLOWED event = NotifiableEvent( owner=owner, notification_type=nt, ) if timestamp is None: timestamp = datetime.datetime.now() metadata['timestamp'] = unicode(timestamp) event.info = render_as_json(metadata) event.save() event.register() return event @staticmethod def register_event_content_shared(): #FIXME: Implementation pending pass
class AuditPhrase(models.Model): phrase = models.CharField(max_length=200) active = models.BooleanField(default=True) def __unicode__(self): return self.phrase
class Biology(Occurrence): infraspecificepithet = models.CharField(null=True, blank=True, max_length=50) infraspecificrank = models.CharField(null=True, blank=True, max_length=50) authoryearofscientificname = models.CharField(null=True, blank=True, max_length=50) nomenclaturalcode = models.CharField(null=True, blank=True, max_length=50) identificationqualifier = models.CharField(null=True, blank=True, max_length=50) identifiedby = models.CharField(null=True, blank=True, max_length=100) dateidentified = models.DateTimeField(null=True, blank=True) typestatus = models.CharField(null=True, blank=True, max_length=50) sex = models.CharField(null=True, blank=True, max_length=50) lifestage = models.CharField(null=True, blank=True, max_length=50) preparations = models.CharField(null=True, blank=True, max_length=50) morphobanknum = models.IntegerField(null=True, blank=True) side = models.CharField(null=True, blank=True, max_length=50, choices=SIDE_VOCABULARY) attributes = models.CharField(null=True, blank=True, max_length=50) faunanotes = models.TextField(null=True, blank=True, max_length=64000) toothupperorlower = models.CharField(null=True, blank=True, max_length=50) toothnumber = models.CharField(null=True, blank=True, max_length=50) toothtype = models.CharField(null=True, blank=True, max_length=50) umtoothrowlengthmm = models.FloatField(null=True, blank=True) um1lengthmm = models.FloatField(null=True, blank=True) um1widthmm = models.FloatField(null=True, blank=True) um2lengthmm = models.FloatField(null=True, blank=True) um2widthmm = models.FloatField(null=True, blank=True) um3lengthmm = models.FloatField(null=True, blank=True) um3widthmm = models.FloatField(null=True, blank=True) lmtoothrowlengthmm = models.FloatField(null=True, blank=True) lm1length = models.FloatField(null=True, blank=True) lm1width = models.FloatField(null=True, blank=True) lm2length = models.FloatField(null=True, blank=True) lm2width = models.FloatField(null=True, blank=True) lm3length = models.FloatField(null=True, blank=True) lm3width = models.FloatField(null=True, blank=True) element = models.CharField(null=True, blank=True, max_length=50) elementmodifier = models.CharField(null=True, blank=True, max_length=50) uli1 = models.BooleanField(default=False) uli2 = models.BooleanField(default=False) uli3 = models.BooleanField(default=False) uli4 = models.BooleanField(default=False) uli5 = models.BooleanField(default=False) uri1 = models.BooleanField(default=False) uri2 = models.BooleanField(default=False) uri3 = models.BooleanField(default=False) uri4 = models.BooleanField(default=False) uri5 = models.BooleanField(default=False) ulc = models.BooleanField(default=False) urc = models.BooleanField(default=False) ulp1 = models.BooleanField(default=False) ulp2 = models.BooleanField(default=False) ulp3 = models.BooleanField(default=False) ulp4 = models.BooleanField(default=False) urp1 = models.BooleanField(default=False) urp2 = models.BooleanField(default=False) urp3 = models.BooleanField(default=False) urp4 = models.BooleanField(default=False) ulm1 = models.BooleanField(default=False) ulm2 = models.BooleanField(default=False) ulm3 = models.BooleanField(default=False) urm1 = models.BooleanField(default=False) urm2 = models.BooleanField(default=False) urm3 = models.BooleanField(default=False) lli1 = models.BooleanField(default=False) lli2 = models.BooleanField(default=False) lli3 = models.BooleanField(default=False) lli4 = models.BooleanField(default=False) lli5 = models.BooleanField(default=False) lri1 = models.BooleanField(default=False) lri2 = models.BooleanField(default=False) lri3 = models.BooleanField(default=False) lri4 = models.BooleanField(default=False) lri5 = models.BooleanField(default=False) llc = models.BooleanField(default=False) lrc = models.BooleanField(default=False) llp1 = models.BooleanField(default=False) llp2 = models.BooleanField(default=False) llp3 = models.BooleanField(default=False) llp4 = models.BooleanField(default=False) lrp1 = models.BooleanField(default=False) lrp2 = models.BooleanField(default=False) lrp3 = models.BooleanField(default=False) lrp4 = models.BooleanField(default=False) llm1 = models.BooleanField(default=False) llm2 = models.BooleanField(default=False) llm3 = models.BooleanField(default=False) lrm1 = models.BooleanField(default=False) lrm2 = models.BooleanField(default=False) lrm3 = models.BooleanField(default=False) taxon = models.ForeignKey(Taxon, related_name='mlp_biology_occurrences') identification_qualifier = models.ForeignKey( IdentificationQualifier, related_name='mlp_biology_occurrences') class Meta: verbose_name = "MLP Biology" verbose_name_plural = "MLP Biology" #db_table='mlp_biology' def __unicode__(self): return str(self.taxon.__unicode__())
class Occurrence(PaleoCoreOccurrenceBaseClass): """ Occurrence <- PaleoCoreOccurrenceBaseClass <- PaleoCoreGeomBaseClass <- PaleoCoreBaseClass PCBC: name, *date_created, *date_last_modified, *problem, *problem_comment, *remarks, *last_import PCGBC: georeference_remarks, *geom, *objects PCOBC: *barcode, *date_recorded, *year_collected, *field_number """ basis_of_record = models.CharField( "Basis of Record", max_length=50, blank=True, null=False, choices=BASIS_OF_RECORD_VOCABULARY) # NOT NULL item_type = models.CharField("Item Type", max_length=255, blank=True, null=False, choices=ITEM_TYPE_VOCABULARY) # NOT NULL collection_code = models.CharField("Collection Code", max_length=20, blank=True, null=True, default='MUR') item_number = models.IntegerField("Item #", null=True, blank=True) item_part = models.CharField("Item Part", max_length=10, null=True, blank=True) catalog_number = models.CharField("Catalog #", max_length=255, blank=True, null=True) # remarks = models.TextField(max_length=255, null=True, blank=True) # in PCBC OK but changing TextField to CKTextF item_scientific_name = models.CharField("Sci Name", max_length=255, null=True, blank=True) item_description = models.CharField("Description", max_length=255, blank=True, null=True) georeference_remarks = models.CharField(max_length=50, null=True, blank=True) collecting_method = models.CharField("Collecting Method", max_length=50, choices=COLLECTING_METHOD_VOCABULARY, null=False) # NOT NULL related_catalog_items = models.CharField("Related Catalog Items", max_length=50, null=True, blank=True) collector = models.CharField(max_length=50, blank=True, null=True, choices=COLLECTOR_CHOICES) finder = models.CharField(max_length=50, blank=True, null=True) disposition = models.CharField(max_length=255, blank=True, null=True) individual_count = models.IntegerField(blank=True, null=True, default=1) preparation_status = models.CharField(max_length=50, blank=True, null=True) stratigraphic_marker_upper = models.CharField(max_length=255, blank=True, null=True) distance_from_upper = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_marker_lower = models.CharField(max_length=255, blank=True, null=True) distance_from_lower = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_marker_found = models.CharField(max_length=255, blank=True, null=True) distance_from_found = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_marker_likely = models.CharField(max_length=255, blank=True, null=True) distance_from_likely = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_member = models.CharField(max_length=255, blank=True, null=True) analytical_unit = models.CharField("Submember", max_length=255, blank=True, null=True) analytical_unit_2 = models.CharField(max_length=255, blank=True, null=True) analytical_unit_3 = models.CharField(max_length=255, blank=True, null=True) in_situ = models.BooleanField(default=False) image = models.FileField(max_length=255, blank=True, upload_to="uploads/images/omo_mursi", null=True) weathering = models.SmallIntegerField(blank=True, null=True) surface_modification = models.CharField(max_length=255, blank=True, null=True) class Meta: managed = True verbose_name = 'Omo Mursi Occurrence' verbose_name_plural = 'Omo Mursi Occurrences' def __unicode__(self): """ What is the best string representation for an occurrence instance? All collected items have catalogue numbers, but observations do not This method returns the catalog number if it exists, or a string with the id value if there is no catalog number. """ if self.catalog_number: return self.catalog_number else: return "item " + str(self.id) @staticmethod def fields_to_display(): fields = ("id", "barcode") return fields
class Event(models.Model): class Meta: verbose_name_plural = 'Events' def __unicode__(self): return u'%s/// %s' % (self.owner, self.name) events = models.Manager() future_events = FutureManager() future_events_without_annotation = FutureWithoutAnnotationsManager() featured_events = FeaturedManager() archived_events = ArchivedManager() created = models.DateTimeField(auto_now_add=True, default=datetime.datetime.now()) modified = models.DateTimeField(auto_now=True, default=datetime.datetime.now()) authentication_key = models.CharField(max_length=40) owner = models.ForeignKey(User, blank=True, null=True) venue_account_owner = models.ForeignKey('accounts.VenueAccount', blank=True, null=True, on_delete=models.SET_NULL) email = models.CharField('email address', max_length=100) name = models.CharField('event title', max_length=250) description = RichTextField(blank=True) location = models.PointField() venue = models.ForeignKey('Venue', blank=True, null=True) price = models.CharField('event price (optional)', max_length=40, blank=True, default='Free') website = models.URLField(blank=True, null=True, default='') tickets = models.CharField('tickets', max_length=250, blank=True, null=True) audited = models.BooleanField(default=False) event_type = models.CharField(max_length=10, choices=EVENT_TYPES, default="SINGLE") tags = TaggableManager() def save(self, *args, **kwargs): if self.pk is None: self.authentication_key = ''.join( random.choice(string.ascii_letters + '0123456789') for x in xrange(40)) self.name_changed = has_changed(self, 'name') super(Event, self).save(*args, **kwargs) return self def get_absolute_url(self): return reverse('event_view', kwargs={'slug': self.slug}) def tags_representation(self): return ", ".join([tag.name for tag in self.tags.all()]) def clean(self): if self.name and slugify(self.name) == '': raise ValidationError('Please enter a name for your event.') def is_featured(self): return self.featuredevent_set.filter( start_time__lte=datetime.datetime.now(), end_time__gte=datetime.datetime.now(), active=True).count() > 0 def has_featured(self): return self.featuredevent_set.filter( end_time__gte=datetime.datetime.now()).count() > 0 def is_multiday(self): return self.event_type == 'MULTIDAY' def is_tickets_field_url(self): url_validator = URLValidator() try: tickets_url = self.tickets if not tickets_url.startswith('http'): tickets_url = "http://%s" % self.tickets url_validator(tickets_url) return True except ValidationError: return False def tickets_url(self): if not self.tickets.startswith('http'): return "http://%s" % self.tickets else: return self.tickets def next_day(self): try: return SingleEvent.objects.filter(end_time__gte=datetime.datetime.now(), event=self)\ .filter(is_occurrence=False)\ .order_by("start_time")[0] except Exception: return None def base(self): return self def event_identifier(self): return self.id def event_description(self): return self.description def is_fb_posted(self): return self.post_to_facebook and self.facebook_event @property def first_occurrence(self): occurrences = self.single_events.all() first_occurrence = None for occurence in occurrences: if not first_occurrence or first_occurrence.start_time > occurence.start_time: first_occurrence = occurence return first_occurrence @property def last_occurrence(self): occurrences = self.single_events.all() last_occurrence = None for occurence in occurrences: if not last_occurrence or last_occurrence.start_time < occurence.start_time: last_occurrence = occurence return last_occurrence @property def sorted_images(self): return self.eventimage_set.order_by("order") @property def sorted_images_tail(self): return self.sorted_images[1:] @property def image(self): try: return self.sorted_images[0] except: return None @property def image_name(self): try: return self.sorted_images[0].picture.name except Exception: return '' @property def slug(self): available_slugs = self.eventslug_set.all() for available_slug in available_slugs: if available_slug.is_primary: return available_slug.slug return '' @property def extended_name(self): return '%s - %s' % (self.name, self.venue.city.name_std) @property def picture(self): try: return self.sorted_images[0].picture except: return None @property def tags_as_string(self): tags = [tag.name for tag in self.tags.all()] return ', '.join(tags) @staticmethod def featured_events_for_region(region): if region: region_id = region.id else: region_id = None return Event.featured_events.filter( Q(featuredevent__all_of_canada=True) | Q(featuredevent__regions__id=region_id)).order_by('?').annotate( Count("id")) def venue_events(self, location, exclude_id=None, limit=36): by_tags_ids = self._get_similar_events_ids_by_tags() events = self.__class__.future_events.filter_by_location(location.location_type, location.location_id)\ .filter(Q(venue_id=self.venue.id) | Q(id__in=by_tags_ids)).order_by('?') result, count = [], 0 for event in events: next_day = event.next_day() if next_day and (not exclude_id or next_day.id != exclude_id): result.append(next_day) count += 1 if count >= limit: break return result def _get_similar_events_ids_by_tags(self): tags = list(self.tags.all().values_list('name', flat=True)) try: tags.remove(u'Wheelchair') except ValueError: pass ids = self.__class__.events.filter( tagged_items__tag__name__in=tags).annotate( repeat_count=Count('id')).filter( repeat_count__gte=2 # match at least two tags ).values_list('id', flat=True) return list(set(ids))
class Site(IadBaseClass): """SITE is the highest class in the database and includes mostly geographical and administrative information about the area where past human activity has been recognized. It is defined by a spatial polygon""" cadastral_community = models.ManyToManyField( Municipality, blank=True, verbose_name="Municipality", help_text="The municipality where the site is located.", related_name="has_sites" ) sm_adm = models.TextField( blank=True, verbose_name="Smallest Administrative Unit", help_text="Smallest Administrative Unit." ) cadastral_number = models.CharField( blank=True, null=True, max_length=250, verbose_name="Cadastral Number (will be moved to Place-Class)", help_text="The cadastral number." ) heritage_number = models.CharField( blank=True, null=True, max_length=250, verbose_name="Heritage Register Number", help_text="The heritage register number." ) plot_number = models.CharField( blank=True, null=True, max_length=250, verbose_name="Plot Number", help_text="The plot number (applies to Slovenian sites)." ) ownership = models.CharField( blank=True, null=True, verbose_name="Ownership", help_text="Ownership of the land, where the site is located.", max_length=250, choices=SITE_OWNERSHIP ) other_period = models.ManyToManyField( SkosConcept, blank=True, verbose_name="Other Present Periods", help_text="Other periods that were recorded on the site.", related_name="has_other_period" ) accessibility = models.CharField( blank=True, null=True, verbose_name="Accessibility", help_text="Transportation types available on the site.", max_length=250, choices=SITE_ACCESSIBILITY ) visibility = models.CharField( blank=True, null=True, verbose_name="Visibility", help_text="How visible are the remains on site.", max_length=250, choices=SITE_VISIBILITY ) infrastructure = models.CharField( blank=True, null=True, verbose_name="Infrastructure", help_text="What kind of infrastructure is available in the vicinity of the site\ (restaurants, parking, etc.)", max_length=250, choices=SITE_INFRASTRUCTURE ) long_term_management = models.CharField( blank=True, null=True, verbose_name="Long-Term Management", help_text="What kind of management of the site is foreseen?", max_length=250, choices=SITE_LONGTERMMANGEMENT ) potential_surrounding = models.CharField( blank=True, null=True, verbose_name="Potential of the Surroundings", help_text="How well is the region where the site is located visited by tourists?\ What is the potential of other touristic attractions in the vicinity?", max_length=250, choices=SITE_POTENTIALSURROUNDINGS ) museum = models.ManyToManyField( Institution, blank=True, verbose_name="Museum", help_text="Where are the finds from the site stored?", related_name="is_museum" ) iad_app = models.BooleanField( verbose_name="App", default=False, choices=BOOLEAN_CHOICES, help_text="Should this site be used in the IAD-App?" ) app_description = models.TextField( blank=True, null=True, verbose_name="App Description", help_text="If the site is going to be used in the IAD app, please provide the \ description of the site to be implemented into the app." ) tourism_comment = models.TextField( blank=True, null=True, help_text="Any noteworthy information about the touristic potential of the site that \ has not been expressed in other fields.", verbose_name="Comment" ) site_checked_by = models.ForeignKey( User, blank=True, null=True, related_name="site_checked_by_user", verbose_name="Checked by", help_text="Who and when checked the entered data (The 'when' is stored automatically).", on_delete=models.SET_NULL ) site_start_date = models.IntegerField( blank=True, null=True, verbose_name="Earliest related Archaeological Entity", help_text="Calculated automatically" ) site_end_date = models.IntegerField( blank=True, null=True, verbose_name="Latest related Archaeological Entity", help_text="Calculated automatically" ) temp_extent = IntegerRangeField(blank=True, null=True) class Meta: ordering = ['pk'] verbose_name = "Site" def get_geojson(self): geojson = serialize( 'geojson', Site.objects.filter(id=self.id), geometry_field='polygon', fields=('name', 'identifier', 'pk') ) return geojson def get_point(self): if self.centroid: geojson = serialize( 'geojson', Site.objects.filter(id=self.id), geometry_field='centroid', fields=('name', 'identifier', 'pk') ) else: geojson = None return geojson @classmethod def get_listview_url(self): return reverse('browsing:browse_sites') @classmethod def get_dl_url(self): return reverse('browsing:dl_sites') @classmethod def get_createview_url(self): return reverse('archiv:site_create') def convex_hull_archents(self): if self.has_archent.exclude(polygon=None): try: geojson = json.loads( self.has_archent.exclude(polygon=None) .aggregate(combined=Union('polygon'))['combined'] .convex_hull.geojson ) geojson['properties'] = { 'name': "Convex hull of all Archaeological Entities" } geojson = json.dumps(geojson) except: geojson = None return geojson else: return None def get_next(self): next = Site.objects.filter(id__gt=self.id) if next: return next.first().id return False def get_prev(self): prev = Site.objects.filter(id__lt=self.id).order_by('-id') if prev: return prev.first().id return False def get_next_public(self): next = Site.objects.filter(id__gt=self.id)\ .exclude(site_checked_by=None).exclude(public=False) if next: return next.first().id return False def get_prev_public(self): prev = Site.objects.filter(id__lt=self.id)\ .exclude(site_checked_by=None).exclude(public=False).order_by('-id') if prev: return prev.first().id return False def get_absolute_url(self): return reverse( 'archiv:site_detail', kwargs={'pk': self.id} ) def get_from_to(self): archents = ArchEnt.objects.filter(site_id__in=[self]) periods = Period.objects.filter(has_archents__in=archents) from_to = periods.aggregate(Max('start_date'), Min('end_date_latest')) return from_to def save(self, *args, **kwargs): from_to = self.get_from_to() self.site_start_date = from_to['start_date__max'] self.site_end_date = from_to['end_date_latest__min'] if self.site_start_date: neg_start = 0 - self.site_start_date else: neg_start = None if self.site_end_date: neg_end = 0 - self.site_end_date else: neg_end = None print("{} - {}".format(neg_start, neg_end)) self.temp_extent = NumericRange(lower=neg_start, upper=neg_end) super().save(*args, **kwargs) def __str__(self): if self.name: return "{}".format(self.name) else: return "{}".format(self.id)
class HelpRequest(models.Model): title = models.CharField( "Título del pedido", max_length=200, help_text="Descripción corta de que estás necesitando", db_index=True, ) message = models.TextField( "Descripción del pedido", help_text=mark_safe( "Acá podes contar detalladamente lo que necesitas, <b>cuanto mejor cuentes tu situación es más probable que te quieran ayudar</b>" ), max_length=2000, null=True, db_index=True, ) name = models.CharField("Nombre y Apellido", max_length=200) phone = models.CharField("Teléfono de contacto", max_length=30) address = models.CharField( "Dirección", help_text= "Es opcional pero puede ayudar a quien quiera ayudarte saber la dirección, ciudad, barrio, referencias, o como llegar", max_length=400, null=True) location = models.PointField( "Ubicación", help_text=mark_safe( 'Seleccioná tu ubicación para que la gente pueda encontrarte, si no querés marcar tu casa una buena opción puede ser la comisaría más cercana o algún otro sitio público cercano.\ <br>Si tenés problemas con este paso <a href="#" class="is-link modal-button" data-target="#myModal" aria-haspopup="true">mirá esta ayuda</a>' ), srid=4326, ) picture = models.ImageField( "Foto", upload_to=rename_img, help_text= "Si querés podés adjuntar una foto relacionada con tu pedido, es opcional pero puede ayudar a que la gente entienda mejor tu situación", null=True, blank=True, ) active = models.BooleanField(default=True, db_index=True) added = models.DateTimeField("Agregado", auto_now_add=True, null=True, blank=True, db_index=True) upvotes = models.IntegerField(default=0, blank=True) downvotes = models.IntegerField(default=0, blank=True) city = models.CharField(max_length=30, blank=True, default="", editable=False) city_code = models.CharField(max_length=30, blank=True, default="", editable=False) @property def thumb(self): filepath, extension = path.splitext(self.picture.url) return f"{filepath}_th{extension}" def _get_city(self): geolocator = Nominatim(user_agent="ayudapy") cordstr = "%s, %s" % self.location.coords[::-1] location = geolocator.reverse(cordstr, language='es') city = '' if location.raw.get('address'): if location.raw['address'].get('city'): city = location.raw['address']['city'] elif location.raw['address'].get('town'): city = location.raw['address']['town'] elif location.raw['address'].get('locality'): city = location.raw['address']['locality'] return city def save(self): from unidecode import unidecode city = self._get_city() self.city = city self.city_code = unidecode(city).replace(" ", "_") return super(HelpRequest, self).save() def __str__(self): return f"<Pedido #{self.id} - {self.name}>"
class HDXExportRegion(models.Model): # noqa """ Mutable database table for hdx - additional attributes on a Job.""" PERIOD_CHOICES = ( ('6hrs', 'Every 6 hours'), ('daily', 'Every day'), ('weekly', 'Every Sunday'), ('monthly', 'The 1st of every month'), ('disabled', 'Disabled'), ) HOUR_CHOICES = zip(xrange(0, 24), xrange(0, 24)) schedule_period = models.CharField(blank=False, max_length=10, default="disabled", choices=PERIOD_CHOICES) schedule_hour = models.IntegerField(blank=False, choices=HOUR_CHOICES, default=0) deleted = models.BooleanField(default=False) # a job should really be required, but that interferes with DRF validation lifecycle. job = models.ForeignKey(Job, null=True, related_name='hdx_export_region_set') is_private = models.BooleanField(default=False) locations = ArrayField(models.CharField(blank=False, max_length=32), null=True) license = models.CharField(max_length=32, null=True, blank=True) subnational = models.BooleanField(default=True) extra_notes = models.TextField(null=True, blank=True) class Meta: # noqa db_table = 'hdx_export_regions' def __str__(self): return self.name + " (prefix: " + self.dataset_prefix + ")" def clean(self): if self.job and not re.match(r'^[a-z0-9-_]+$', self.job.name): raise ValidationError({ 'dataset_prefix': "Invalid dataset_prefix: {0}".format(self.job.name) }) @property def delta(self): # noqa if self.schedule_period == '6hrs': return timedelta(hours=6) if self.schedule_period == 'daily': return timedelta(days=1) if self.schedule_period == 'weekly': return timedelta(days=7) if self.schedule_period == 'monthly': return timedelta(days=31) @property def next_run(self): # noqa now = timezone.now().replace(minute=0, second=0, microsecond=0) if self.schedule_period == '6hrs': delta = 6 - (self.schedule_hour + now.hour % 6) return now + timedelta(hours=delta) now = now.replace(hour=self.schedule_hour) if self.schedule_period == 'daily': anchor = now if timezone.now() < anchor: return anchor return anchor + timedelta(days=1) if self.schedule_period == 'weekly': # adjust so the week starts on Sunday anchor = now - timedelta((now.weekday() + 1) % 7) if timezone.now() < anchor: return anchor return anchor + timedelta(days=7) if self.schedule_period == 'monthly': (_, num_days) = calendar.monthrange(now.year, now.month) anchor = now.replace(day=1) if timezone.now() < anchor: return anchor return anchor + timedelta(days=num_days) @property def last_run(self): # noqa if self.job.runs.count() > 0: return self.job.runs.all()[self.job.runs.count() - 1].finished_at @property def last_size(self): if self.job.runs.count() > 0: return self.job.runs.all()[self.job.runs.count() - 1].size @property def buffer_aoi(self): # noqa return self.job.buffer_aoi @property def name(self): # noqa return self.job.description @property def dataset_prefix(self): # noqa return self.job.name @property def the_geom(self): return self.job.the_geom @property def simplified_geom(self): # noqa return self.job.simplified_geom @property def feature_selection(self): # noqa return self.job.feature_selection @property def export_formats(self): # noqa return self.job.export_formats @property def datasets(self): # noqa return self.hdx_dataset.dataset_links(settings.HDX_URL_PREFIX) @property def job_uid(self): return self.job.uid @property def hdx_dataset(self): # noqa """ Initialize an HDXExportSet corresponding to this Model. """ # # TODO make distinction between GOESGeom/GeoJSON better return HDXExportSet( data_update_frequency=self.update_frequency, dataset_prefix=self.dataset_prefix, extent=self.job.the_geom, extra_notes=self.extra_notes, feature_selection=self.job.feature_selection_object, is_private=self.is_private, license=self.license, locations=self.locations, name=self.name, subnational=self.subnational, ) @property def update_frequency(self): """Update frequencies in HDX form.""" if self.schedule_period == '6hrs': return 1 if self.schedule_period == 'daily': return 1 if self.schedule_period == 'weekly': return 7 if self.schedule_period == 'monthly': return 30 return 0 def sync_to_hdx(self): # noqa LOG.info("HDXExportRegion.sync_to_hdx called.") self.hdx_dataset.sync_datasets()
class Announcement(models.Model): ''' METADATA ''' class Meta: app_label = 'tenant_foundation' db_table = 'nwapp_announcements' verbose_name = _('Announcement') verbose_name_plural = _('Announcements') default_permissions = () permissions = () ''' OBJECT MANAGERS ''' objects = AnnouncementManager() ''' MODEL FIELDS ''' text = models.CharField( _("Text"), max_length=31, help_text=_('The text content of this announcement.'), db_index=True, unique=True) is_archived = models.BooleanField( _("Is Archived"), help_text=_('Indicates whether announcement was archived.'), default=False, blank=True, db_index=True) # AUDITING FIELDS uuid = models.CharField( _("UUID"), help_text= _('The unique identifier we want to release to the public to identify this unique record.' ), default=uuid.uuid4, editable=False, max_length=63, # Do not change unique=True, db_index=True, ) slug = models.SlugField( _("Slug"), help_text=_('The unique identifier used externally.'), max_length=255, null=False, unique=True, db_index=True, ) created_at = models.DateTimeField(auto_now_add=True, db_index=True) created_by = models.ForeignKey( SharedUser, help_text=_('The user whom created this object.'), related_name="created_announcements", on_delete=models.SET_NULL, blank=True, null=True, ) created_from = models.GenericIPAddressField( _("Created from"), help_text=_('The IP address of the creator.'), blank=True, null=True) created_from_is_public = models.BooleanField( _("Is the IP "), help_text=_('Is creator a public IP and is routable.'), default=False, blank=True) created_from_position = models.PointField( _("Created from position"), help_text=_('The latitude and longitude coordinates for the creator.'), srid=4326, geography=True, null=True, blank=True, ) last_modified_at = models.DateTimeField(auto_now=True) last_modified_by = models.ForeignKey( SharedUser, help_text=_('The user whom modified this object last.'), related_name="last_modified_announcements", on_delete=models.SET_NULL, blank=True, null=True, ) last_modified_from = models.GenericIPAddressField( _("Last modified from"), help_text=_('The IP address of the modifier.'), blank=True, null=True) last_modified_from_is_public = models.BooleanField( _("Is the IP "), help_text=_('Is modifier a public IP and is routable.'), default=False, blank=True) last_modified_from_position = models.PointField( _("Last modified from position"), help_text=_( 'The latitude and longitude coordinates for the last modified user.' ), srid=4326, geography=True, null=True, blank=True, ) """ MODEL FUNCTIONS """ def __str__(self): return str(self.text) @transaction.atomic def save(self, *args, **kwargs): ''' Override the `save` function to support extra functionality of our model. ''' ''' If we are creating a new row, then we will automatically increment the `id` field instead of relying on Postgres DB. ''' if self.id == None: try: latest_obj = Announcement.objects.latest('id') self.id = latest_obj.id + 1 except Announcement.DoesNotExist: self.id = 1 # The following code will generate a unique slug and if the slug # is not unique in the database, then continue to try generating # a unique slug until it is found. if self.slug == "" or self.slug == None: slug = slugify(self.text) while Announcement.objects.filter(slug=slug).exists(): slug = slugify(self.text) + "-" + get_referral_code(16) self.slug = Truncator(slug).chars(255) ''' Finally call the parent function which handles saving so we can carry out the saving operation by Django in our ORM. ''' super(Announcement, self).save(*args, **kwargs)
class Monitor(models.Model): COUNTIES = Choices(*County.names) LOCATION = Choices('inside', 'outside') PAYLOAD_SCHEMA = None DEFAULT_SENSOR = None id = SmallUUIDField(default=uuid_default(), primary_key=True, db_index=True, editable=False, verbose_name='ID') name = models.CharField(max_length=250) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) access_key = models.UUIDField(default=uuid.uuid4) is_hidden = models.BooleanField(default=False, help_text="Hides the monitor on the map.") is_sjvair = models.BooleanField( default=False, help_text="Is this monitor part of the SJVAir network?") # Where is this sensor setup? position = models.PointField(null=True, db_index=True) county = models.CharField(max_length=20, blank=True, choices=COUNTIES) location = models.CharField(max_length=10, choices=LOCATION) latest = JSONField(encoder=JSONEncoder, default=dict) pm25_calibration_formula = models.CharField(max_length=255, blank=True, default='') objects = InheritanceManager() class Meta: ordering = ('name', ) def __str__(self): return self.name @property def device(self): return self.__class__.__name__ @property def is_active(self): if not self.latest: return False now = timezone.now() cutoff = timedelta(seconds=60 * 10) return (now - parse_datetime(self.latest['timestamp'])) < cutoff def create_entry(self, payload, sensor=None): return Entry( monitor=self, payload=payload, sensor=sensor or '', position=self.position, location=self.location, is_processed=False, ) def process_entry(self, entry): entry.calibrate_pm25(self.pm25_calibration_formula) entry.calculate_aqi() entry.calculate_averages() entry.is_processed = True return entry def set_latest(self, entry): from camp.api.v1.monitors.serializers import EntrySerializer fields = ['id'] + EntrySerializer.fields + EntrySerializer.value_fields self.latest = serialize(entry, fields=fields) def save(self, *args, **kwargs): if self.position: # TODO: Can we do this only when self.position is updated? self.county = County.lookup(self.position) super().save(*args, **kwargs)
class Job(models.Model): """ Database model for an 'Export'. Immutable, except in the case of HDX Export Regions. """ id = models.AutoField(primary_key=True, editable=False) uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False, db_index=True) user = models.ForeignKey(User, related_name='owner') name = models.CharField(max_length=100, db_index=True, blank=False) description = models.CharField(max_length=1000, db_index=True, default='', blank=True) event = models.CharField(max_length=100, db_index=True, default='', blank=True) export_formats = ArrayField(models.CharField(max_length=10), validators=[validate_export_formats], blank=False) published = models.BooleanField(default=False, db_index=True) the_geom = models.GeometryField(verbose_name='Uploaded geometry', srid=4326, blank=False) simplified_geom = models.GeometryField(verbose_name='Simplified geometry', srid=4326, blank=True, null=True) objects = models.GeoManager() feature_selection = models.TextField( blank=False, validators=[validate_feature_selection]) created_at = models.DateTimeField(default=timezone.now, editable=False) updated_at = models.DateTimeField(default=timezone.now, editable=False) mbtiles_maxzoom = models.IntegerField(null=True, blank=True) mbtiles_minzoom = models.IntegerField(null=True, blank=True) mbtiles_source = models.TextField(null=True, blank=True) # flags buffer_aoi = models.BooleanField(default=False) unlimited_extent = models.BooleanField(default=False) hidden = models.BooleanField(default=False) # hidden from the list page expire_old_runs = models.BooleanField(default=True) per_theme = models.BooleanField(default=False) class Meta: # pragma: no cover managed = True db_table = 'jobs' @property def osma_link(self): bounds = self.the_geom.extent return "http://osm-analytics.org/#/show/bbox:{0},{1},{2},{3}/buildings/recency".format( *bounds) @property def feature_selection_object(self): """ a valid FeatureSelection object based off the feature_selection text column. """ return FeatureSelection(self.feature_selection) @property def area(self): return get_geodesic_area(self.the_geom) def save(self, *args, **kwargs): self.simplified_geom = simplify_geom(self.the_geom, force_buffer=self.buffer_aoi) super(Job, self).save(*args, **kwargs) def __str__(self): return str(self.uid)
class Entry(models.Model): ENVIRONMENT = [ 'celcius', 'fahrenheit', 'humidity', 'pressure', 'pm10_env', 'pm25_env', 'pm100_env', 'pm10_standard', 'pm25_standard', 'pm100_standard', 'particles_03um', 'particles_05um', 'particles_10um', 'particles_25um', 'particles_50um', 'particles_100um', ] id = SmallUUIDField(default=uuid_default(), primary_key=True, db_index=True, editable=False, verbose_name='ID') created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) timestamp = models.DateTimeField(default=timezone.now) monitor = models.ForeignKey('monitors.Monitor', related_name='entries', on_delete=models.CASCADE) sensor = models.CharField(max_length=50, blank=True, default='', db_index=True) # Where was the monitor when this entry was logged? position = models.PointField(null=True, db_index=True) location = models.CharField(max_length=10, choices=Monitor.LOCATION) # Has the raw data been calibrated and processed? is_processed = models.BooleanField(default=False, db_index=True) # Original payload from the device / api payload = JSONField(encoder=JSONEncoder, default=dict) pm25_calibration_formula = models.CharField(max_length=255, blank=True, default='') # Post-processed, calibrated data celcius = models.DecimalField(max_digits=5, decimal_places=1, null=True) fahrenheit = models.DecimalField(max_digits=5, decimal_places=1, null=True) humidity = models.DecimalField(max_digits=5, decimal_places=1, null=True) pressure = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm10_env = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm25_env = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm100_env = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm10_standard = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm25_standard = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm100_standard = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm25_avg_15 = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm25_avg_60 = models.DecimalField(max_digits=7, decimal_places=2, null=True) pm25_aqi = models.IntegerField(null=True) particles_03um = models.DecimalField(max_digits=7, decimal_places=2, null=True) particles_05um = models.DecimalField(max_digits=7, decimal_places=2, null=True) particles_10um = models.DecimalField(max_digits=7, decimal_places=2, null=True) particles_25um = models.DecimalField(max_digits=7, decimal_places=2, null=True) particles_50um = models.DecimalField(max_digits=7, decimal_places=2, null=True) particles_100um = models.DecimalField(max_digits=7, decimal_places=2, null=True) class Meta: constraints = (models.UniqueConstraint( fields=['monitor', 'timestamp', 'sensor'], name='unique_entry'), ) indexes = (BrinIndex(fields=['timestamp', 'sensor'], autosummarize=True), ) ordering = ('-timestamp', ) def get_calibration_context(self): return { field: float(getattr(self, field, None) or 0) for field in self.ENVIRONMENT } def get_average(self, field, minutes): values = list( Entry.objects.filter(monitor=self.monitor, sensor=self.sensor, timestamp__range=( self.timestamp - timedelta(minutes=minutes), self.timestamp, )).exclude(pk=self.pk, **{ f'{field}__isnull': True }).values_list(field, flat=True)) if getattr(self, field) is not None: values.append(Decimal(getattr(self, field))) if values: return sum(values) / len(values) return 0 def calibrate_pm25(self, formula): if formula: parser = ExpressionParser() expression = parser.parse(formula) context = self.get_calibration_context() self.pm25_env = expression.evaluate(context) self.pm25_calibration_formula = formula def calculate_aqi(self): algo = aqi.get_algo(aqi.ALGO_EPA) try: self.pm25_aqi = algo.iaqi( aqi.POLLUTANT_PM25, min(self.get_average('pm25_env', 60 * 12), algo.piecewise['bp'][aqi.POLLUTANT_PM25][-1][1])) except Exception: # python-aqi often errors on high numbers because it # doesn't account for calculations above 500. Since AQI # only goes to 500, just set it to the max. (Yikes!) self.pm25_aqi = 500 def calculate_averages(self): self.pm25_avg_15 = self.get_average('pm25_env', 15) self.pm25_avg_60 = self.get_average('pm25_env', 60) def save(self, *args, **kwargs): # Temperature adjustments if self.fahrenheit is None and self.celcius is not None: self.fahrenheit = (Decimal(self.celcius) * (Decimal(9) / Decimal(5))) + 32 if self.celcius is None and self.fahrenheit is not None: self.celcius = (Decimal(self.fahrenheit) - 32) * (Decimal(5) / Decimal(9)) return super().save(*args, **kwargs)
class License(models.Model): class Meta(object): verbose_name = "Licence" verbose_name_plural = "Licences" slug = models.SlugField( verbose_name="Slug", max_length=100, blank=True, unique=True, db_index=True, primary_key=True, ) title = models.TextField( verbose_name="Titre", ) alternate_titles = ArrayField( models.TextField(), verbose_name="Autres titres", blank=True, null=True, size=None, ) url = models.URLField( verbose_name="URL", blank=True, null=True, ) alternate_urls = ArrayField( models.URLField(), verbose_name="Autres URLs", null=True, blank=True, size=None, ) domain_content = models.BooleanField( verbose_name="Domain Content", default=False, ) domain_data = models.BooleanField( default=False, verbose_name="Domain Data", ) domain_software = models.BooleanField( default=False, verbose_name="Domain Software", ) STATUS_CHOICES = ( ('active', 'Active'), ('deleted', 'Deleted'), ) status = models.CharField( verbose_name="Status", max_length=7, choices=STATUS_CHOICES, default='active', ) maintainer = models.TextField( verbose_name="Maintainer", null=True, blank=True, ) CONFORMANCE_CHOICES = ( ('approved', 'Approved'), ('not reviewed', 'Not reviewed'), ('rejected', 'Rejected'), ) od_conformance = models.CharField( verbose_name="Open Definition Conformance", max_length=30, choices=CONFORMANCE_CHOICES, default='not reviewed', ) osd_conformance = models.CharField( verbose_name="Open Source Definition Conformance", max_length=30, choices=CONFORMANCE_CHOICES, default='not reviewed', ) def __str__(self): return self.title @property def ckan_id(self): return self.slug
class Notification(AuditableModel): notified_user = models.ForeignKey(User, verbose_name=strings.NOTIFIED_USER, related_name='notifications') notification_type = models.PositiveIntegerField( verbose_name=strings.NOTIFICATION_TYPE, choices=constants.NOTIFICATION_TYPE_CHOICES) info = models.TextField(verbose_name=strings.NOTIFICATION_INFO) displayed = models.BooleanField( verbose_name=strings.NOTIFICATION_DISPLAYED, default=False) class Meta: verbose_name = strings.NOTIFICATION_VERBOSE_NAME verbose_name_plural = strings.NOTIFICATION_VERBOSE_NAME_PLURAL def save(self): """Parent save method, here the mails are sent""" super(Notification, self).save() # get user recipent us = self.notified_user # check that user has a valid email address if us.email.find('@') > 0 and us.email.find('.') > 0: # mandatory fields subject = strings.EMAIL_NOTIFICATION_SUBJECT to = us.email from_email = settings.DEFAULT_FROM_EMAIL # get text version of the message text_content = self.get_email_content_from_type( self.notification_type) # FIXME: HTML version implementation pending html_content = self.get_email_content_from_type( self.notification_type) msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) msg.attach_alternative(html_content, "text/html") msg.send() def get_email_content_from_type(self, type): """Returns the content of the mail according to the not_type""" if type is 1: return strings.CIRCUIT_CREATED_EMAIL_NOTIFICATION # case circuit is favorited if type is 2: # parse the route from info textfield route = self.info[self.info.find('circuit'):] route = route[:route.find('\n')] route = route[route.find(':') + 2:] try: circuit = Circuit.objects.get(pk=route) circuit_name = circuit.name except Circuit.DoesNotExist: circuit_name = 'Route :)' # parse the user from info textfield us = self.info[self.info.find('user') + 7:] us = us[:us.find(',')] try: user = User.objects.get(pk=us) user = user.get_full_name() except User.DoesNotExist: user = '******' return strings.CIRCUIT_FAVORITED_EMAIL_NOTIFICATION % { 'route': circuit_name, 'user': user, } if type is 3: # parse the route from info textfield route = self.info[self.info.find('original_circuit') + 19:] route = route[:route.find(',')] try: circuit = Circuit.objects.get(pk=route) circuit_name = circuit.name except Circuit.DoesNotExist: circuit_name = 'Route :)' # parse the user from info textfield us = self.info[self.info.find('user') + 7:] us = us[:us.find(',')] try: user = User.objects.get(pk=us) user = user.get_full_name() except User.DoesNotExist: user = '******' return strings.CIRCUIT_REMIXED_EMAIL_NOTIFICATION % { 'route': circuit_name, 'user': user, } if type is 4: return strings.CIRCUIT_UPDATED_EMAIL_NOTIFICATION if type is 5: return strings.USER_FOLLOWED_EMAIL_NOTIFICATION if type is 6: return strings.CONTENT_SHARED_EMAIL_NOTIFICATION def get_html_email_content_from_type(self, type): """Returns the content of the mail according to the not_type in html form""" # WORK IN PROGRESS template = 'mails/notification.html' context_dict = {} context_dict['notified_user'] = self.notified_user context_dict['type'] = type info_dict = json.loads(self.info) if type is 1: try: author = User.objects.get(pk=info_dict['author']) except User.DoesNotExist: author = None try: circuit = Circuit.objects.get(pk=info_dict['circuit']) except Circuit.DoesNotExist: circuit = None context_dict['circuit'] = circuit context_dict['notification_msg'] = \ strings.CIRCUIT_CREATED_EMAIL_NOTIFICATION # case circuit is favorited if type is 2: try: circuit = Circuit.objects.get(pk=info_dict['circuit']) except Circuit.DoesNotExist: circuit = None try: user = User.objects.get(pk=info_dict['user']) except User.DoesNotExist: user = None return strings.CIRCUIT_FAVORITED_EMAIL_NOTIFICATION % { 'route': circuit_name, 'user': user, } if type is 3: # parse the route from info textfield route = self.info[self.info.find('original_circuit') + 19:] route = route[:route.find(',')] try: circuit = Circuit.objects.get(pk=route) circuit_name = circuit.name except Circuit.DoesNotExist: circuit_name = 'Route :)' # parse the user from info textfield us = self.info[self.info.find('user') + 7:] us = us[:us.find(',')] try: user = User.objects.get(pk=us) user = user.get_full_name() except User.DoesNotExist: user = '******' return strings.CIRCUIT_REMIXED_EMAIL_NOTIFICATION % { 'route': circuit_name, 'user': user, } if type is 4: return strings.CIRCUIT_UPDATED_EMAIL_NOTIFICATION if type is 5: return strings.USER_FOLLOWED_EMAIL_NOTIFICATION if type is 6: return strings.CONTENT_SHARED_EMAIL_NOTIFICATION def make_info_dict(self): return json.loads(self.info) def get_restful_url(self): return "%s%s" % (settings.API_V1_PREFIX.rstrip('/'), reverse('notification_resource', kwargs={'notification_id': self.id})) def get_restful_link_metadata(self): metadata = OrderedDict() metadata['href'] = self.get_restful_url() metadata['rel'] = 'alternate' metadata['title'] = self.notification_type metadata['type'] = 'application/json' return metadata @staticmethod def get_notifications(user): return self.objects.filter(notified_user=user)
class Classroom(models.Model): students = models.ManyToManyField(Person, null=True) active = models.BooleanField(null=True)
class GenericImportEvent(models.Model): class Meta: abstract = True PENDING_VERIFICATION = 1 VERIFIYING = 2 FINISHED_VERIFICATION = 3 CREATING = 4 FINISHED_CREATING = 5 FAILED_FILE_VERIFICATION = 6 # Original Name of the file file_name = models.CharField(max_length=255) # Global errors and notices (json) errors = models.TextField(default='') field_order = models.TextField(default='') # Metadata about this particular import owner = models.ForeignKey(User) created = models.DateTimeField(auto_now=True) completed = models.DateTimeField(null=True, blank=True) status = models.IntegerField(default=PENDING_VERIFICATION) # When false, this dataset is in 'preview' mode # When true this dataset has been written to the # database commited = models.BooleanField(default=False) def status_summary(self): if self.status == GenericImportEvent.PENDING_VERIFICATION: return "Not Yet Started" elif self.status == GenericImportEvent.VERIFIYING: return "Verifying" elif self.status == GenericImportEvent.FINISHED_VERIFICATION: return "Verification Complete" elif self.status == GenericImportEvent.CREATING: return "Creating Trees" elif self.status == GenericImportEvent.FAILED_FILE_VERIFICATION: return "Invalid File Structure" else: return "Finished" def active(self): return self.status != GenericImportEvent.FINISHED_CREATING def row_type_counts(self): q = self.row_set()\ .values('status')\ .annotate(Count('status')) return {r['status']: r['status__count'] for r in q} def update_status(self): """ Update the status field based on current row statuses """ pass def append_error(self, err, data=None): code, msg, fatal = err if self.errors is None or self.errors == '': self.errors = '[]' self.errors = json.dumps(self.errors_as_array() + [{ 'code': code, 'msg': msg, 'data': data, 'fatal': fatal }]) return self def errors_as_array(self): if self.errors is None or self.errors == '': return [] else: return json.loads(self.errors) def has_errors(self): return len(self.errors_as_array()) > 0 def row_set(self): raise Exception('Abstract Method') def rows(self): return self.row_set().order_by('idx').all() def validate_main_file(self): raise Exception('Abstract Method')
class Person(models.Model): gender = models.CharField(max_length=1, choices=GENDER_CHOICES) # Jards Macalé is an amazing brazilian musician! =] enjoy_jards_macale = models.BooleanField(default=True) like_metal_music = models.BooleanField(default=False) name = models.CharField(max_length=30) nickname = models.SlugField(max_length=36) age = models.IntegerField() bio = models.TextField() birthday = models.DateField() birth_time = models.TimeField() appointment = models.DateTimeField() blog = models.URLField() occupation = models.CharField(max_length=10, choices=OCCUPATION_CHOICES) uuid = models.UUIDField(primary_key=False) name_hash = models.BinaryField(max_length=16) days_since_last_login = models.BigIntegerField() duration_of_sleep = models.DurationField() email = models.EmailField() id_document = models.CharField(unique=True, max_length=10) try: from django.db.models import JSONField data = JSONField() except ImportError: # Skip JSONField-related fields pass try: from django.contrib.postgres.fields import ArrayField, HStoreField from django.contrib.postgres.fields import JSONField as PostgresJSONField from django.contrib.postgres.fields.citext import ( CICharField, CIEmailField, CITextField, ) from django.contrib.postgres.fields.ranges import ( BigIntegerRangeField, DateRangeField, DateTimeRangeField, IntegerRangeField, ) if settings.USING_POSTGRES: acquaintances = ArrayField(models.IntegerField()) postgres_data = PostgresJSONField() hstore_data = HStoreField() ci_char = CICharField(max_length=30) ci_email = CIEmailField() ci_text = CITextField() int_range = IntegerRangeField() bigint_range = BigIntegerRangeField() date_range = DateRangeField() datetime_range = DateTimeRangeField() except ImportError: # Skip PostgreSQL-related fields pass try: from django.contrib.postgres.fields.ranges import FloatRangeField if settings.USING_POSTGRES: float_range = FloatRangeField() except ImportError: # Django version greater or equal than 3.1 pass try: from django.contrib.postgres.fields.ranges import DecimalRangeField if settings.USING_POSTGRES: decimal_range = DecimalRangeField() except ImportError: # Django version lower than 2.2 pass if BAKER_GIS: geom = models.GeometryField() point = models.PointField() line_string = models.LineStringField() polygon = models.PolygonField() multi_point = models.MultiPointField() multi_line_string = models.MultiLineStringField() multi_polygon = models.MultiPolygonField() geom_collection = models.GeometryCollectionField()
class GenericImportRow(models.Model): """ A row of data and import status Subclassed by 'Tree Import Row' and 'Species Import Row' """ class Meta: abstract = True # JSON dictionary from header <-> rows data = models.TextField() # Row index from original file idx = models.IntegerField() finished = models.BooleanField(default=False) # JSON field containing error information errors = models.TextField(default='') # Status WAITING = 3 status = models.IntegerField(default=WAITING) def __init__(self, *args, **kwargs): super(GenericImportRow, self).__init__(*args, **kwargs) self.jsondata = None self.cleaned = {} @property def model_fields(self): raise Exception('Abstract Method') @property def datadict(self): if self.jsondata is None: self.jsondata = json.loads(self.data) return self.jsondata @datadict.setter def datadict(self, v): self.jsondata = v self.data = json.dumps(self.jsondata) def errors_as_array(self): if self.errors is None or self.errors == '': return [] else: return json.loads(self.errors) def has_errors(self): return len(self.errors_as_array()) > 0 def get_fields_with_error(self): data = {} datadict = self.datadict for e in self.errors_as_array(): for field in e['fields']: data[field] = datadict[field] return data def has_fatal_error(self): if self.errors: for err in json.loads(self.errors): if err['fatal']: return True return False def append_error(self, err, fields, data=None): code, msg, fatal = err if self.errors is None or self.errors == '': self.errors = '[]' # If you give append_error a single field # there is no need to get angry if isinstance(fields, basestring): fields = (fields, ) # make into tuple self.errors = json.dumps( json.loads(self.errors) + [{ 'code': code, 'fields': fields, 'msg': msg, 'data': data, 'fatal': fatal }]) return self def safe_float(self, fld): try: return float(self.datadict[fld]) except: self.append_error(errors.FLOAT_ERROR, fld) return False def safe_bool(self, fld): """ Returns a tuple of (success, bool value) """ v = self.datadict.get(fld, '').lower() if v == '': return (True, None) if v == 'true' or v == 't' or v == 'yes': return (True, True) elif v == 'false' or v == 'f' or v == 'no': return (True, False) else: self.append_error(errors.BOOL_ERROR, fld) return (False, None) def safe_int(self, fld): try: return int(self.datadict[fld]) except: self.append_error(errors.INT_ERROR, fld) return False def safe_pos_int(self, fld): i = self.safe_int(fld) if i is False: return False elif i < 0: self.append_error(errors.POS_INT_ERROR, fld) return False else: return i def safe_pos_float(self, fld): i = self.safe_float(fld) if i is False: return False elif i < 0: self.append_error(errors.POS_FLOAT_ERROR, fld) return False else: return i def convert_units(self, data, converts): INCHES_TO_DBH_FACTOR = 1.0 / settings.DBH_TO_INCHES_FACTOR # Similar to tree for fld, factor in converts.iteritems(): if fld in data and factor != 1.0: data[fld] = float(data[fld]) * factor * INCHES_TO_DBH_FACTOR def validate_numeric_fields(self): def cleanup(fields, fn): has_errors = False for f in fields: if f in self.datadict and self.datadict[f]: maybe_num = fn(f) if maybe_num is False: has_errors = True else: self.cleaned[f] = maybe_num return has_errors pfloat_ok = cleanup(self.model_fields.POS_FLOAT_FIELDS, self.safe_pos_float) float_ok = cleanup(self.model_fields.FLOAT_FIELDS, self.safe_float) int_ok = cleanup(self.model_fields.POS_INT_FIELDS, self.safe_pos_int) return pfloat_ok and float_ok and int_ok def validate_boolean_fields(self): has_errors = False for f in self.model_fields.BOOLEAN_FIELDS: if f in self.datadict: success, v = self.safe_bool(f) if success and v is not None: self.cleaned[f] = v else: has_errors = True return has_errors def validate_choice_fields(self): has_errors = False for field, choice_key in self.model_fields.CHOICE_MAP.iteritems(): value = self.datadict.get(field, None) if value: all_choices = settings.CHOICES[choice_key] choices = {value: id for (id, value) in all_choices} if value in choices: self.cleaned[field] = choices[value] else: has_errors = True self.append_error(errors.INVALID_CHOICE, field, choice_key) return has_errors def validate_string_fields(self): has_errors = False for field in self.model_fields.STRING_FIELDS: value = self.datadict.get(field, None) if value: if len(value) > 255: self.append_error(errors.STRING_TOO_LONG, field) has_errors = True else: self.cleaned[field] = value return has_errors def validate_date_fields(self): has_errors = False for field in self.model_fields.DATE_FIELDS: value = self.datadict.get(field, None) if value: try: datep = datetime.strptime(value, '%Y-%m-%d') self.cleaned[self.model_fields.DATE_PLANTED] = datep except ValueError, e: self.append_error(errors.INVALID_DATE, self.model_fields.DATE_PLANTED) has_errors = True return has_errors
class IadBaseClass(IdProvider): """A class for shared properties""" identifier = models.CharField( blank=True, null=True, max_length=250, verbose_name="Identifier" ) name = models.CharField( blank=True, null=True, max_length=250, verbose_name="The objects name" ) alt_name = models.ManyToManyField( AltName, blank=True, verbose_name="Alternative Name", help_text="Another name of the site (another spelling, language, alias name etc.)." ) alt_id = models.CharField( blank=True, null=True, max_length=250, verbose_name="Alternative ID", help_text="Any other official identifier of this entity." ) description = models.TextField( blank=True, null=True, verbose_name="Description of the object." ) comment = models.TextField( blank=True, null=True, help_text="""Any noteworthy general information about the object that cannot be expressed in other fields.""", verbose_name="Comment" ) public = models.BooleanField( default=False, verbose_name="Public", choices=BOOLEAN_CHOICES, help_text="Should this entry (and all related entries) be public\ or only visible to the account holders? Can be made public\ only after data-check was completed." ) literature = models.ManyToManyField( Reference, blank=True, verbose_name="Literature", help_text="Add publication references" ) polygon = models.MultiPolygonField(blank=True, null=True) polygon_proxy = models.BooleanField( default=False, verbose_name="No precise polygon", choices=BOOLEAN_CHOICES, help_text="Please set to 'Yes' in case the polygon is merely a place holder" ) centroid = models.PointField(blank=True, null=True) def save(self, *args, **kwargs): if self.polygon and not self.centroid: cent = self.polygon.centroid self.centroid = cent super().save(*args, **kwargs) @classmethod def get_points(self): model_name = self.__name__ ct = ContentType.objects.get( app_label='archiv', model=model_name.lower() ).model_class() geojson = serialize( 'geojson', ct.objects.all(), geometry_field='centroid', fields=( 'name', 'pk', ) ) return geojson @classmethod def get_shapes(self): model_name = self.__name__ ct = ContentType.objects.get( app_label='archiv', model=model_name.lower() ).model_class() geojson = serialize( 'geojson', ct.objects.all(), geometry_field='polygon', fields=( 'name', 'pk', ) ) return geojson @classmethod def get_convex_hull(self): if Site.objects.exclude(polygon=None): sites = [x.id for x in Site.objects.exclude(polygon=None) if x.polygon.valid] valid_sites = Site.objects.filter(id__in=sites) geojson = json.loads( valid_sites.exclude(polygon=None) .aggregate(combined=Union('polygon'))['combined'] .convex_hull.geojson ) geojson['properties'] = { 'name': "Convex hull of all {} objects".format(self.__name__) } geojson = json.dumps(geojson) return geojson else: None def copy_instance(self): """Saves a copy of the current object and returns it""" obj = self obj.id = None obj.pk = None old_name = "{}".format(self.name) if old_name: obj.name = "COPY OF {}".format(old_name) else: obj.name = "COPY of some other Object" obj.save() return obj class Meta: abstract = True
class SpeciesImportRow(GenericImportRow): SUCCESS = 0 ERROR = 1 VERIFIED = 4 SPECIES_MAP = { 'symbol': fields.species.USDA_SYMBOL, 'alternate_symbol': fields.species.ALT_SYMBOL, 'itree_code': fields.species.ITREE_CODE, 'genus': fields.species.GENUS, 'species': fields.species.SPECIES, 'cultivar_name': fields.species.CULTIVAR, 'common_name': fields.species.COMMON_NAME, 'native_status': fields.species.NATIVE_STATUS, 'fall_conspicuous': fields.species.FALL_COLORS, 'palatable_human': fields.species.EDIBLE, 'flower_conspicuous': fields.species.FLOWERING, 'bloom_period': fields.species.FLOWERING_PERIOD, 'fruit_period': fields.species.FRUIT_PERIOD, 'wildlife_value': fields.species.WILDLIFE, 'v_max_dbh': fields.species.MAX_DIAMETER, 'v_max_height': fields.species.MAX_HEIGHT, 'fact_sheet': fields.species.FACT_SHEET, 'family': fields.species.FAMILY, 'other_part_of_name': fields.species.OTHER_PART_OF_NAME, 'id': fields.species.ID, 'tree_count': fields.species.TREE_COUNT, 'scientific_name': fields.species.SCIENTIFIC_NAME, } # Species reference species = models.ForeignKey(Species, null=True, blank=True) merged = models.BooleanField(default=False) import_event = models.ForeignKey(SpeciesImportEvent) def diff_from_species(self, species): """ Compute how this row is different from the given species The result is a json dict with field names: { '<field name>': ['<species value>', '<row value>'] } Note that you can't *remove* data with species import If the returned dictionary is empty, importing this row will (essentially) be a nop This should only be called after a verify because I uses cleaned data """ #TODO: Test me if species is None: return {} data = self.cleaned rslt = {} for (modelkey, rowkey) in SpeciesImportRow.SPECIES_MAP.iteritems(): rowdata = data.get(rowkey, None) modeldata = getattr(species, modelkey) if rowdata and rowdata != modeldata: rslt[rowkey] = (modeldata, rowdata) # Always include the ID rslt['id'] = (species.pk, None) return rslt @property def model_fields(self): return fields.species def validate_species(self): genus = self.datadict.get(fields.species.GENUS, '') species = self.datadict.get(fields.species.SPECIES, '') cultivar = self.datadict.get(fields.species.CULTIVAR, '') other_part = self.datadict.get(fields.species.OTHER_PART_OF_NAME, '') # Save these as "empty" strings self.cleaned[fields.species.GENUS] = genus self.cleaned[fields.species.SPECIES] = species self.cleaned[fields.species.CULTIVAR] = cultivar self.cleaned[fields.species.OTHER_PART_OF_NAME] = other_part if genus != '' or species != '' or cultivar != '' or other_part != '': matching_species = Species.objects\ .filter(genus__iexact=genus)\ .filter(species__iexact=species)\ .filter(cultivar_name__iexact=cultivar)\ .filter(other_part_of_name__iexact=other_part) self.cleaned[fields.species.ORIG_SPECIES]\ |= { s.pk for s in matching_species } return True def validate_code(self, fld, species_fld, addl_filters=None): value = self.datadict.get(fld, None) if value: self.cleaned[fld] = value matching_species = Species.objects\ .filter(**{species_fld: value}) if addl_filters: matching_species = matching_species\ .filter(**addl_filters) self.cleaned[fields.species.ORIG_SPECIES]\ |= { s.pk for s in matching_species } return True def validate_usda_code(self): # USDA codes don't cover cultivars, so assert that # a 'matching' species *must* have the same cultivar # and same USDA code addl_filter = { 'cultivar_name': self.cleaned.get(fields.species.CULTIVAR, '') } return self.validate_code(fields.species.USDA_SYMBOL, 'symbol', addl_filter) def validate_alt_code(self): return self.validate_code(fields.species.ALT_SYMBOL, 'alternate_symbol') def validate_required_fields(self): req = { fields.species.GENUS, fields.species.COMMON_NAME, fields.species.ITREE_CODE } has_errors = False for field in req: value = self.cleaned.get(field, None) if not value: has_errors = True self.append_error(errors.MISSING_FIELD, field) return not has_errors def validate_itree_code(self): has_error = False itreecode = self.datadict.get(fields.species.ITREE_CODE) if itreecode: rsrc = Resource.objects.filter(meta_species=itreecode) if len(rsrc) == 0: has_error = True self.append_error(errors.INVALID_ITREE_CODE, (fields.species.ITREE_CODE, )) else: self.cleaned[fields.species.RESOURCE] = rsrc[0] else: has_error = True self.append_error(errors.MISSING_ITREE_CODE, (fields.species.ITREE_CODE, )) return not has_error def validate_row(self): """ Validate a row. Returns True if there were no fatal errors, False otherwise The method mutates self in two ways: - The 'errors' field on self will be appended to whenever an error is found - The 'cleaned' field on self will be set as fields get validated """ # Clear errrors self.errors = '' # NOTE: Validations append errors directly to importrow # and move data over to the 'cleaned' hash as it is # validated # Convert all fields to correct datatypes self.validate_and_convert_datatypes() # Check to see if this species matches any existing ones # they'll be stored as a set of ORIG_SPECIES self.cleaned[fields.species.ORIG_SPECIES] = set() self.validate_species() self.validate_usda_code() self.validate_alt_code() self.validate_itree_code() self.validate_required_fields() # Native status is a horrible field that pretends to # be a boolean value but is actually a string so we # change it here if fields.species.NATIVE_STATUS in self.cleaned: self.cleaned[fields.species.NATIVE_STATUS] = str( self.cleaned[fields.species.NATIVE_STATUS]) # If same is set to true this is essentially a no-op same = False possible_matches = self.cleaned[fields.species.ORIG_SPECIES] # TODO: Certain fields require this flag to be reset if not self.merged: if len(possible_matches) == 0: self.merged = True else: species = [ Species.objects.get(pk=pk) for pk in possible_matches ] diffs = [self.diff_from_species(s) for s in species] # There's always a single field that has changed in the # diff. This is the 'id' field of the existing species, # which will never be the same as the None for the current # id. if all([diff.keys() == ['id'] for diff in diffs]): self.merged = True same = True else: diff_keys = set() for diff in diffs: for key in diff.keys(): diff_keys.add(key) if len(possible_matches) > 1: self.append_error(errors.TOO_MANY_SPECIES, tuple(diff_keys), tuple(diffs)) else: self.append_error(errors.MERGE_REQ, tuple(diff_keys), diffs[0]) pk = list(possible_matches)[0] self.species = Species.objects.get(pk=pk) fatal = False if self.has_fatal_error(): self.status = SpeciesImportRow.ERROR fatal = True elif same: # Nothing changed, this has been effectively added self.status = SpeciesImportRow.SUCCESS else: self.status = SpeciesImportRow.VERIFIED self.save() return not fatal def commit_row(self): # First validate if not self.validate_row(): return False # Get our data data = self.cleaned species_edited = False # Initially grab species from row if it exists # and edit it species = self.species # If not specified create a new one if species is None: species = Species() # Convert units self.convert_units( data, { fields.species.MAX_DIAMETER: self.import_event.max_diameter_conversion_factor, fields.species.MAX_HEIGHT: self.import_event.max_tree_height_conversion_factor }) #TODO: Update tree count nonsense for modelkey, importdatakey in SpeciesImportRow.SPECIES_MAP.iteritems( ): importdata = data.get(importdatakey, None) if importdata is not None: species_edited = True setattr(species, modelkey, importdata) if species_edited: species.save() resource = data[fields.species.RESOURCE] species.resource.clear() species.resource.add(resource) species.save() resource.save() self.species = species self.status = TreeImportRow.SUCCESS self.save() return True
class Biology(Occurrence): infraspecific_epithet = models.CharField(null=True, blank=True, max_length=50) infraspecific_rank = models.CharField(null=True, blank=True, max_length=50) author_year_of_scientific_name = models.CharField(null=True, blank=True, max_length=50) nomenclatural_code = models.CharField(null=True, blank=True, max_length=50) identified_by = models.CharField(null=True, blank=True, max_length=100) date_identified = models.DateTimeField(null=True, blank=True) type_status = models.CharField(null=True, blank=True, max_length=50) sex = models.CharField(null=True, blank=True, max_length=50) life_stage = models.CharField(null=True, blank=True, max_length=50) preparations = models.CharField(null=True, blank=True, max_length=50) morphobank_number = models.IntegerField(null=True, blank=True) side = models.CharField(null=True, blank=True, max_length=50, choices=SIDE_VOCABULARY) attributes = models.CharField(null=True, blank=True, max_length=50) fauna_notes = models.TextField(null=True, blank=True, max_length=64000) tooth_upper_or_lower = models.CharField(null=True, blank=True, max_length=10) tooth_number = models.CharField(null=True, blank=True, max_length=50) tooth_type = models.CharField(null=True, blank=True, max_length=50) um_tooth_row_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_1_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_1_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_2_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_2_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_3_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_3_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_tooth_row_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_1_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_1_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_2_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_2_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_3_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_3_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) element = models.CharField(null=True, blank=True, max_length=50) element_modifier = models.CharField(null=True, blank=True, max_length=50) uli1 = models.BooleanField(default=False) uli2 = models.BooleanField(default=False) uli3 = models.BooleanField(default=False) uli4 = models.BooleanField(default=False) uli5 = models.BooleanField(default=False) uri1 = models.BooleanField(default=False) uri2 = models.BooleanField(default=False) uri3 = models.BooleanField(default=False) uri4 = models.BooleanField(default=False) uri5 = models.BooleanField(default=False) ulc = models.BooleanField(default=False) urc = models.BooleanField(default=False) ulp1 = models.BooleanField(default=False) ulp2 = models.BooleanField(default=False) ulp3 = models.BooleanField(default=False) ulp4 = models.BooleanField(default=False) urp1 = models.BooleanField(default=False) urp2 = models.BooleanField(default=False) urp3 = models.BooleanField(default=False) urp4 = models.BooleanField(default=False) ulm1 = models.BooleanField(default=False) ulm2 = models.BooleanField(default=False) ulm3 = models.BooleanField(default=False) urm1 = models.BooleanField(default=False) urm2 = models.BooleanField(default=False) urm3 = models.BooleanField(default=False) lli1 = models.BooleanField(default=False) lli2 = models.BooleanField(default=False) lli3 = models.BooleanField(default=False) lli4 = models.BooleanField(default=False) lli5 = models.BooleanField(default=False) lri1 = models.BooleanField(default=False) lri2 = models.BooleanField(default=False) lri3 = models.BooleanField(default=False) lri4 = models.BooleanField(default=False) lri5 = models.BooleanField(default=False) llc = models.BooleanField(default=False) lrc = models.BooleanField(default=False) llp1 = models.BooleanField(default=False) llp2 = models.BooleanField(default=False) llp3 = models.BooleanField(default=False) llp4 = models.BooleanField(default=False) lrp1 = models.BooleanField(default=False) lrp2 = models.BooleanField(default=False) lrp3 = models.BooleanField(default=False) lrp4 = models.BooleanField(default=False) llm1 = models.BooleanField(default=False) llm2 = models.BooleanField(default=False) llm3 = models.BooleanField(default=False) lrm1 = models.BooleanField(default=False) lrm2 = models.BooleanField(default=False) lrm3 = models.BooleanField(default=False) taxon = models.ForeignKey( Taxon, default=0, on_delete=models.SET_DEFAULT, # prevent deletion when taxa deleted related_name='omo_mursi_biology_occurrences') identification_qualifier = models.ForeignKey( IdentificationQualifier, null=True, blank=True, on_delete=models.SET_NULL, related_name='omo_mursi_biology_occurrences') class Meta: verbose_name = "Omo Mursi Biology" verbose_name_plural = "Omo Mursi Biology" def __str__(self): return str(self.taxon.__str__())
class User(AbstractBaseUser, PermissionsMixin): email = models.EmailField( verbose_name=_("email address"), unique=True, blank=True, error_messages={"unique": _("There is another user with this email")}, ) name = models.CharField(_("Name of User"), blank=True, max_length=255) permissions = models.ManyToManyField("users.Permission", verbose_name=_("Permissions")) completed_missions = models.ManyToManyField( "rewards.Mission", blank=True, verbose_name=_("Completed missions")) is_staff = models.BooleanField( _("staff status"), default=False, help_text=_( "Designates whether the user can log into this admin site."), ) is_active = models.BooleanField( verbose_name=_("active"), default=True, help_text=_( "Designates whether this user should be treated as active. " "Unselect this instead of deleting accounts."), ) date_joined = models.DateTimeField(_("Date joined"), default=timezone.now) objects = UserManager() EMAIL_FIELD = "email" USERNAME_FIELD = "email" @property def token(self): return self._generate_jwt_token() @property def is_rider(self): return self.permissions.filter(rol=RIDER).exists() def _generate_jwt_token(self): """ Generates a JSON Web Token that stores this user's ID and has an expiry date set to 60 days into the future. """ dt = datetime.now() + timedelta(days=60) token = jwt.encode( { "id": self.pk, "exp": int(dt.strftime("%s")) }, settings.SECRET_KEY, algorithm="HS256", ) return token.decode("utf-8") class Meta: verbose_name = _("User") verbose_name_plural = _("Users") ordering = ("date_joined", ) def __str__(self): return self.name
class Intervention(AddPropertyMixin, MapEntityMixin, AltimetryMixin, TimeStampedModelMixin, StructureRelated, NoDeleteMixin): name = models.CharField(verbose_name=_(u"Name"), max_length=128, db_column='nom', help_text=_(u"Brief summary")) date = models.DateField(default=datetime.now, verbose_name=_(u"Date"), db_column='date', help_text=_(u"When ?")) subcontracting = models.BooleanField(verbose_name=_(u"Subcontracting"), default=False, db_column='sous_traitance') # Technical information width = models.FloatField(default=0.0, verbose_name=_(u"Width"), db_column='largeur') height = models.FloatField(default=0.0, verbose_name=_(u"Height"), db_column='hauteur') area = models.FloatField(editable=False, default=0, verbose_name=_(u"Area"), db_column='surface') # Costs material_cost = models.FloatField(default=0.0, verbose_name=_(u"Material cost"), db_column='cout_materiel') heliport_cost = models.FloatField(default=0.0, verbose_name=_(u"Heliport cost"), db_column='cout_heliport') subcontract_cost = models.FloatField(default=0.0, verbose_name=_(u"Subcontract cost"), db_column='cout_soustraitant') """ Topology can be of type Infrastructure or of own type Intervention """ topology = models.ForeignKey(Topology, null=True, # TODO: why null ? related_name="interventions_set", verbose_name=_(u"Interventions")) # AltimetyMixin for denormalized fields from related topology, updated via trigger. length = models.FloatField(editable=True, default=0.0, null=True, blank=True, db_column='longueur', verbose_name=_(u"3D Length")) stake = models.ForeignKey('core.Stake', null=True, related_name='interventions', verbose_name=_("Stake"), db_column='enjeu') status = models.ForeignKey('InterventionStatus', verbose_name=_("Status"), db_column='status') type = models.ForeignKey('InterventionType', null=True, blank=True, verbose_name=_(u"Type"), db_column='type') disorders = models.ManyToManyField('InterventionDisorder', related_name="interventions", db_table="m_r_intervention_desordre", verbose_name=_(u"Disorders"), blank=True) jobs = models.ManyToManyField('InterventionJob', through='ManDay', verbose_name=_(u"Jobs")) project = models.ForeignKey('Project', null=True, blank=True, related_name="interventions", verbose_name=_(u"Project"), db_column='chantier') description = models.TextField(blank=True, verbose_name=_(u"Description"), db_column='descriptif', help_text=_(u"Remarks and notes")) objects = NoDeleteMixin.get_manager_cls(InterventionManager)() class Meta: db_table = 'm_t_intervention' verbose_name = _(u"Intervention") verbose_name_plural = _(u"Interventions") def __init__(self, *args, **kwargs): super(Intervention, self).__init__(*args, **kwargs) self._geom = None def set_infrastructure(self, baseinfra): self.topology = baseinfra if not self.on_infrastructure: raise ValueError("Expecting an infrastructure or signage") def default_stake(self): stake = None if self.topology: for path in self.topology.paths.all(): if path.stake > stake: stake = path.stake return stake def reload(self): if self.pk: fromdb = self.__class__.objects.get(pk=self.pk) self.area = fromdb.area AltimetryMixin.reload(self, fromdb) TimeStampedModelMixin.reload(self, fromdb) NoDeleteMixin.reload(self, fromdb) if self.topology: self.topology.reload() return self def save(self, *args, **kwargs): if self.stake is None: self.stake = self.default_stake() super(Intervention, self).save(*args, **kwargs) # Set kind of Intervention topology if self.topology and not self.on_infrastructure: topology_kind = self._meta.object_name.upper() self.topology.kind = topology_kind self.topology.save(update_fields=['kind']) # Invalidate project map if self.project: try: os.remove(self.project.get_map_image_path()) except OSError: pass self.reload() @property def on_infrastructure(self): return self.is_infrastructure or self.is_signage @property def infrastructure(self): """ Equivalent of topology attribute, but casted to related type (Infrastructure) """ if self.is_infrastructure: return self.infrastructures[0] return None @property def signage(self): """ Equivalent of topology attribute, but casted to related type (Signage) """ if self.is_signage: return self.signages[0] return None @classproperty def infrastructure_verbose_name(cls): return _("On") @property def infrastructure_display(self): icon = 'path' title = _('Path') if self.on_infrastructure: icon = self.topology.kind.lower() if self.infrastructure: title = u'%s: %s' % (_(self.topology.kind.capitalize()), self.infrastructure) elif self.signage: title = u'%s: %s' % (_(self.topology.kind.capitalize()), self.signage) return u'<img src="%simages/%s-16.png" title="%s">' % (settings.STATIC_URL, icon, title) @property def infrastructure_csv_display(self): if self.on_infrastructure: if self.infrastructure: return u"%s: %s (%s)" % ( _(self.topology.kind.capitalize()), self.infrastructure, self.infrastructure.pk) elif self.signage: return u"%s: %s (%s)" % ( _(self.topology.kind.capitalize()), self.signage, self.signage.pk) return '' @property def is_infrastructure(self): if self.topology: return self.topology.kind == Infrastructure.KIND return False @property def is_signage(self): if self.topology: return self.topology.kind == Signage.KIND return False @property def in_project(self): return self.project is not None @property def paths(self): if self.topology: return self.topology.paths.all() return Path.objects.none() @property def signages(self): if self.is_signage: return [Signage.objects.existing().get(pk=self.topology.pk)] return [] @property def infrastructures(self): if self.is_infrastructure: return [Infrastructure.objects.existing().get(pk=self.topology.pk)] return [] @property def total_manday(self): total = 0.0 for md in self.manday_set.all(): total += float(md.nb_days) return total @classproperty def total_manday_verbose_name(cls): return _("Mandays") @property def total_cost_mandays(self): total = 0.0 for md in self.manday_set.all(): total += md.cost return total @classproperty def total_cost_mandays_verbose_name(cls): return _("Mandays cost") @property def total_cost(self): return self.total_cost_mandays + \ self.material_cost + \ self.heliport_cost + \ self.subcontract_cost @classproperty def total_cost_verbose_name(cls): return _("Total cost") @classproperty def geomfield(cls): return Topology._meta.get_field('geom') @property def geom(self): if self._geom is None: if self.topology: self._geom = self.topology.geom return self._geom @geom.setter def geom(self, value): self._geom = value @property def name_display(self): return u'<a data-pk="%s" href="%s" title="%s" >%s</a>' % (self.pk, self.get_detail_url(), self.name, self.name) @property def name_csv_display(self): return unicode(self.name) def __unicode__(self): return u"%s (%s)" % (self.name, self.date) @classmethod def path_interventions(cls, path): return cls.objects.existing().filter(topology__aggregations__path=path) @classmethod def topology_interventions(cls, topology): topos = Topology.overlapping(topology).values_list('pk', flat=True) return cls.objects.existing().filter(topology__in=topos).distinct('pk')
class User(AbstractUser): email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) has_profile = models.BooleanField(default=False) middle_name = models.CharField(blank=True, max_length=255, unique=False) bio = models.TextField(blank=True, default='') phone = models.CharField(blank=True, default='', max_length=255) address = models.CharField(blank=True, default='', max_length=255) city = models.CharField(blank=True, default='', max_length=255) state = models.CharField(blank=True, default='', max_length=255) postal_code = models.CharField(blank=True, default='', max_length=255) country = CountryField(blank=True) url = models.URLField(blank=True, default='', max_length=255) geom = models.PointField(blank=True, null=True) roles = models.ManyToManyField( Role, blank=True, ) related_individuals = models.ManyToManyField( 'self', through='mdi.EntitiesEntities') related_organizations = models.ManyToManyField( 'mdi.Organization', through='mdi.EntitiesEntities', through_fields=['from_ind', 'to_org']) languages = models.ManyToManyField( 'mdi.Language', blank=True, ) services = models.ManyToManyField( 'mdi.Service', blank=True, ) field_of_study = models.CharField( blank=True, default='', max_length=254) # Only applies to Researchers. Much still TBD. affiliation = models.TextField( blank=True, default='') # Only applies to Researchers. Much still TBD. projects = models.TextField( blank=True, default='') # Only applies to Researchers. Much still TBD. challenges = models.ManyToManyField( 'mdi.Challenge', blank=True, ) socialnetworks = models.ManyToManyField(SocialNetwork, blank=True, through='UserSocialNetwork') notes = models.TextField(blank=True, default='') source = models.ForeignKey(Source, on_delete=models.CASCADE, default=5) # created_at: would normally add this but django-registration gives us date_joined updated_at = models.DateTimeField(auto_now=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] @classmethod def get_email_field_name(cls): return 'email' class Meta: ordering = [ 'last_name', ] db_table = 'auth_user'
class SingleEvent(models.Model): """ Single event is event that occur only once. So when we create Event that occur more then one time, for it automaticaly will be created single events. """ class Meta: verbose_name_plural = 'Single events' def __unicode__(self): return u'%s/// %s' % (self.event, self.start_time) objects = models.Manager() future_events = FutureEventDayManager() homepage_events = HomePageEventDayManager() featured_events = FeaturedEventDayManager() archived_events = ArchivedEventDayManager() event = models.ForeignKey(Event, blank=False, null=False, related_name='single_events') start_time = models.DateTimeField('starting time', auto_now=False, auto_now_add=False) end_time = models.DateTimeField('ending time (optional)', auto_now=False, auto_now_add=False) description = models.TextField(null=True, blank=True) is_occurrence = models.BooleanField(default=False) viewed = models.IntegerField(default=0) facebook_event = models.ForeignKey('FacebookEvent', blank=True, null=True) def save(self, *args, **kwargs): if self.end_time < self.start_time: self.end_time = dateparser.parse( self.end_time) + datetime.timedelta(days=1) super(SingleEvent, self).save(*args, **kwargs) return self def get_absolute_url(self): if self.event.event_type == 'MULTIDAY': url = reverse('event_view', args=(self.event.slug, )) if self.is_occurrence: url = '%s#day=%s' % (url, self.start_time.strftime('%Y-%m-%d')) return url else: return reverse('event_view', args=(self.event.slug, self.start_time.strftime('%Y-%m-%d'))) def event_description(self): description = self.description if not description: description = self.event.description return description def __getattr__(self, key): if key not in ('event', '_event_cache'): return getattr(self.event, key) raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, key)) def event_identifier(self): return self.event.id def base(self): return self.event @property def first_occurrence(self): occurrences = self.event.single_events.filter(is_occurrence=True) first_occurrence = None for occurence in occurrences: if not first_occurrence or first_occurrence.start_time > occurence.start_time: first_occurrence = occurence return first_occurrence @property def last_occurrence(self): occurrences = self.event.single_events.filter(is_occurrence=True) last_occurrence = None for occurence in occurrences: if not last_occurrence or last_occurrence.start_time < occurence.start_time: last_occurrence = occurence return last_occurrence @property def sorted_occurrences(self): occurrences = self.event.single_events.filter(is_occurrence=True) return sorted(occurrences, key=lambda occurrence: occurrence.start_time) @property def sorted_occurences_for_description(self): keys = [] occurrences = [] for occurrence in self.sorted_occurrences: key = occurrence.start_time.strftime("%m/%d/%Y") if not key in keys: keys.append(key) occurrences.append(occurrence) return occurrences def short_description(self): description = strip_tags(self.event_description()) if len(description) > 255: return '%s...' % description[:255] return description @property def sorted_occurrences_days(self): occurrences_json = OrderedDict() for occurrence in self.sorted_occurrences: key = occurrence.start_time.strftime("%m/%d/%Y") if key in occurrences_json: occurrences_json[key].append({ "start_time": occurrence.start_time, "end_time": occurrence.end_time }) else: occurrences_json[key] = [{ "start_time": occurrence.start_time, "end_time": occurrence.end_time }] return occurrences_json def same_date_events(self): return SingleEvent.future_events.filter( event_id=self.event.id, start_time__startswith=self.start_time.date()).order_by( "start_time")
class Biology(Occurrence): # Biology sex = models.CharField("Sex", null=True, blank=True, max_length=50) life_stage = models.CharField("Life Stage", null=True, blank=True, max_length=50, choices=HRP_LIFE_STAGE_CHOICES) size_class = models.CharField("Size Class", null=True, blank=True, max_length=50, choices=HRP_SIZE_CLASS_CHOICES) # Taxon taxon = models.ForeignKey(Taxon, default=0, on_delete=models.SET_DEFAULT, # prevent deletion when taxa deleted related_name='hrp_taxon_bio_occurrences') identification_qualifier = models.ForeignKey(IdentificationQualifier, null=True, blank=True, on_delete=models.SET_NULL, related_name='hrp_id_qualifier_bio_occurrences') qualifier_taxon = models.ForeignKey(Taxon, null=True, blank=True, on_delete=models.SET_NULL, related_name='hrp_qualifier_taxon_bio_occurrences') verbatim_taxon = models.CharField(null=True, blank=True, max_length=1024) verbatim_identification_qualifier = models.CharField(null=True, blank=True, max_length=255) taxonomy_remarks = models.TextField(max_length=500, null=True, blank=True) # Identification identified_by = models.CharField(null=True, blank=True, max_length=100, choices=HRP_IDENTIFIER_CHOICES) year_identified = models.IntegerField(null=True, blank=True) type_status = models.CharField(null=True, blank=True, max_length=50) fauna_notes = models.TextField(null=True, blank=True, max_length=64000) # Element side = models.CharField("Side", null=True, blank=True, max_length=50, choices=HRP_SIDE_CHOICES) element = models.CharField("Element", null=True, blank=True, max_length=50, choices=HRP_ELEMENT_CHOICES) # TODO add element_modifier choices once field is cleaned element_modifier = models.CharField("Element Mod", null=True, blank=True, max_length=50, choices=HRP_ELEMENT_MODIFIER_CHOICES) # TODO populate portion after migrate element_portion = models.CharField("Element Portion", null=True, blank=True, max_length=50, choices=HRP_ELEMENT_PORTION_CHOICES) # TODO populate number choices after migrate element_number = models.CharField(null=True, blank=True, max_length=50, choices=HRP_ELEMENT_NUMBER_CHOICES) element_remarks = models.TextField(max_length=500, null=True, blank=True) tooth_upper_or_lower = models.CharField(null=True, blank=True, max_length=50) tooth_number = models.CharField(null=True, blank=True, max_length=50) tooth_type = models.CharField(null=True, blank=True, max_length=50) # upper dentition fields uli1 = models.BooleanField(default=False) uli2 = models.BooleanField(default=False) uli3 = models.BooleanField(default=False) uli4 = models.BooleanField(default=False) uli5 = models.BooleanField(default=False) uri1 = models.BooleanField(default=False) uri2 = models.BooleanField(default=False) uri3 = models.BooleanField(default=False) uri4 = models.BooleanField(default=False) uri5 = models.BooleanField(default=False) ulc = models.BooleanField(default=False) urc = models.BooleanField(default=False) ulp1 = models.BooleanField(default=False) ulp2 = models.BooleanField(default=False) ulp3 = models.BooleanField(default=False) ulp4 = models.BooleanField(default=False) urp1 = models.BooleanField(default=False) urp2 = models.BooleanField(default=False) urp3 = models.BooleanField(default=False) urp4 = models.BooleanField(default=False) ulm1 = models.BooleanField(default=False) ulm2 = models.BooleanField(default=False) ulm3 = models.BooleanField(default=False) urm1 = models.BooleanField(default=False) urm2 = models.BooleanField(default=False) urm3 = models.BooleanField(default=False) # lower dentition fields lli1 = models.BooleanField(default=False) lli2 = models.BooleanField(default=False) lli3 = models.BooleanField(default=False) lli4 = models.BooleanField(default=False) lli5 = models.BooleanField(default=False) lri1 = models.BooleanField(default=False) lri2 = models.BooleanField(default=False) lri3 = models.BooleanField(default=False) lri4 = models.BooleanField(default=False) lri5 = models.BooleanField(default=False) llc = models.BooleanField(default=False) lrc = models.BooleanField(default=False) llp1 = models.BooleanField(default=False) llp2 = models.BooleanField(default=False) llp3 = models.BooleanField(default=False) llp4 = models.BooleanField(default=False) lrp1 = models.BooleanField(default=False) lrp2 = models.BooleanField(default=False) lrp3 = models.BooleanField(default=False) lrp4 = models.BooleanField(default=False) llm1 = models.BooleanField(default=False) llm2 = models.BooleanField(default=False) llm3 = models.BooleanField(default=False) lrm1 = models.BooleanField(default=False) lrm2 = models.BooleanField(default=False) lrm3 = models.BooleanField(default=False) # indeterminate dental fields indet_incisor = models.BooleanField(default=False) indet_canine = models.BooleanField(default=False) indet_premolar = models.BooleanField(default=False) indet_molar = models.BooleanField(default=False) indet_tooth = models.BooleanField(default=False) deciduous = models.BooleanField(default=False) # Measurements um_tooth_row_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_1_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_1_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_2_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_2_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_3_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) um_3_width_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_tooth_row_length_mm = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_1_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_1_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_2_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_2_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_3_length = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) lm_3_width = models.DecimalField(max_digits=38, decimal_places=8, null=True, blank=True) # TODO delete attributes, preparations and morphobank number attributes = models.CharField(null=True, blank=True, max_length=50) preparations = models.CharField(null=True, blank=True, max_length=50) morphobank_number = models.IntegerField(null=True, blank=True) # empty, ok to delete def __str__(self): return str(self.taxon.__str__()) class Meta: verbose_name = "HRP Biology" verbose_name_plural = "HRP Biology"
class Occurrence(models.Model): #id = models.IntegerField(primary_key=True) # NOT NULL barcode = models.IntegerField(blank=True, null=True) date_last_modified = models.DateTimeField("Date Last Modified", auto_now_add=True, auto_now=True) basis_of_record = models.CharField( "Basis of Record", max_length=50, blank=True, null=False, choices=BASIS_OF_RECORD_VOCABULARY) # NOT NULL item_type = models.CharField("Item Type", max_length=255, blank=True, null=False, choices=ITEM_TYPE_VOCABULARY) # NOT NULL collection_code = models.CharField("Collection Code", max_length=20, blank=True, null=True, default='MLP') # Note we're not using localities! item_number = models.IntegerField("Item #", max_length=50, null=True, blank=True) item_part = models.CharField("Item Part", max_length=10, null=True, blank=True) catalog_number = models.CharField("Catalog #", max_length=255, blank=True, null=True) # TODO add rich text field for remarks remarks = models.TextField(max_length=255, null=True, blank=True) item_scientific_name = models.CharField("Sci Name", max_length=255, null=True, blank=True) item_description = models.CharField("Item Description", max_length=255, blank=True, null=True) georeference_remarks = models.CharField(max_length=50, null=True, blank=True) collecting_method = models.CharField("Collecting Method", max_length=50, blank=True, choices=COLLECTING_METHOD_VOCABULARY, null=False) # NOT NULL related_catalog_items = models.CharField("Related Catalog Items", max_length=50, null=True, blank=True) collector = models.CharField(max_length=50, blank=True, null=True, choices=COLLECTOR_CHOICES) finder = models.CharField(max_length=50, blank=True, null=True) disposition = models.CharField(max_length=255, blank=True, null=True) field_number = models.DateTimeField(blank=False, null=False, editable=True) # NOT NULL year_collected = models.IntegerField(blank=True, null=True) individual_count = models.IntegerField(blank=True, null=True, default=1) preparation_status = models.CharField(max_length=50, blank=True, null=True) stratigraphic_marker_upper = models.CharField(max_length=255, blank=True, null=True) distance_from_upper = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_marker_lower = models.CharField(max_length=255, blank=True, null=True) distance_from_lower = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_marker_found = models.CharField(max_length=255, blank=True, null=True) distance_from_found = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_marker_likely = models.CharField(max_length=255, blank=True, null=True) distance_from_likely = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) stratigraphic_member = models.CharField(max_length=255, blank=True, null=True) analytical_unit = models.CharField(max_length=255, blank=True, null=True) analyticalunit2 = models.CharField(max_length=255, blank=True, null=True) analyticalunit3 = models.CharField(max_length=255, blank=True, null=True) in_situ = models.BooleanField(default=False) ranked = models.BooleanField(default=False) image = models.FileField(max_length=255, blank=True, upload_to="uploads/images/mlp", null=True) weathering = models.SmallIntegerField(blank=True, null=True) surface_modification = models.CharField(max_length=255, blank=True, null=True) #TODO Change presentation to show only 2 decimal places #point_x = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) # now taken from geom #point_y = models.DecimalField(max_digits=38, decimal_places=8, blank=True, null=True) # now taken from geom problem = models.BooleanField(default=False) problem_comment = models.TextField(max_length=255, blank=True, null=True) geom = models.GeometryField(srid=4326, blank=True, null=True) # NOT NULL objects = models.GeoManager() class Meta: managed = True #db_table = 'mlp_occurrence' verbose_name = 'MLP Occurrence' verbose_name_plural = 'MLP Occurrences' def __unicode__(self): """ What is the best string representation for an occurrence instance? All collected items have catalogue numbers, but observations do not This method returns the catalog number if it exists, or a string with the id value if there is no catalog number. """ if self.catalog_number: return self.catalog_number else: return "item " + str(self.id) def point_x(self): try: return self.geom.x except: return 0 def point_y(self): try: return self.geom.y except: return 0 def easting(self): try: utmPoint = utm.from_latlon(self.geom.coords[1], self.geom.coords[0]) return utmPoint[0] except: return 0 def northing(self): try: utmPoint = utm.from_latlon(self.geom.coords[1], self.geom.coords[0]) return utmPoint[1] except: return 0 @staticmethod def fields_to_display(): fields = ("id", "barcode") return fields
class Occurrence(projects.models.PaleoCoreOccurrenceBaseClass): """ Occurrence == Specimen, a general class for things discovered in the field. Find's have three subtypes: Archaeology, Biology, Geology Fields are grouped by comments into logical sets (i.e. ontological classes) """ basis_of_record = models.CharField("Basis of Record", max_length=50, blank=True, null=False, help_text='e.g. Observed item or Collected item', choices=HRP_BASIS_OF_RECORD_VOCABULARY) # NOT NULL dwc:basisOfRecord field_number = models.CharField("Field Number", max_length=50, null=True, blank=True) item_type = models.CharField("Item Type", max_length=255, blank=True, null=False, choices=ITEM_TYPE_VOCABULARY) # NOT NULL # TODO merge with taxon item_scientific_name = models.CharField("Sci Name", max_length=255, null=True, blank=True) # TODO merge with element item_description = models.CharField("Description", max_length=255, blank=True, null=True) item_count = models.IntegerField("Item Count", blank=True, null=True, default=1) collector = models.CharField("Collector", max_length=50, blank=True, null=True, choices=HRP_COLLECTOR_CHOICES) recorded_by = models.ForeignKey("Person", null=True, blank=True, related_name="occurrence_recorded_by", on_delete=models.SET_NULL) finder = models.CharField("Finder", null=True, blank=True, max_length=50, choices=HRP_COLLECTOR_CHOICES) found_by = models.ForeignKey("Person", null=True, blank=True, related_name="occurrence_found_by", on_delete=models.SET_NULL) collecting_method = models.CharField("Collecting Method", max_length=50, choices=HRP_COLLECTING_METHOD_VOCABULARY, null=True, blank=True) locality = models.ForeignKey("Locality", null=True, blank=True, on_delete=models.SET_NULL) item_number = models.IntegerField("Item #", null=True, blank=True) item_part = models.CharField("Item Part", max_length=10, null=True, blank=True) cat_number = models.CharField("Cat Number", max_length=255, blank=True, null=True) disposition = models.CharField("Disposition", max_length=255, blank=True, null=True) preparation_status = models.CharField("Prep Status", max_length=50, blank=True, null=True) # TODO rename collection remarks to find remarks collection_remarks = models.TextField("Collection Remarks", null=True, blank=True, max_length=255) # Geological Context stratigraphic_formation = models.CharField("Formation", max_length=255, blank=True, null=True) stratigraphic_member = models.CharField("Member", max_length=255, blank=True, null=True) analytical_unit_1 = models.CharField(max_length=255, blank=True, null=True) analytical_unit_2 = models.CharField(max_length=255, blank=True, null=True) analytical_unit_3 = models.CharField(max_length=255, blank=True, null=True) analytical_unit_found = models.CharField(max_length=255, blank=True, null=True) analytical_unit_likely = models.CharField(max_length=255, blank=True, null=True) analytical_unit_simplified = models.CharField(max_length=255, blank=True, null=True) in_situ = models.BooleanField(default=False) ranked = models.BooleanField(default=False) weathering = models.SmallIntegerField(blank=True, null=True) surface_modification = models.CharField("Surface Mod", max_length=255, blank=True, null=True) geology_remarks = models.TextField("Geol Remarks", max_length=500, null=True, blank=True) # Location collection_code = models.CharField("Collection Code", max_length=20, blank=True, null=True) drainage_region = models.CharField("Drainage Region", null=True, blank=True, max_length=255) # Media image = models.FileField(max_length=255, blank=True, upload_to="uploads/images/hrp", null=True) class Meta: verbose_name = "HRP Occurrence" verbose_name_plural = "HRP Occurrences" ordering = ["collection_code", "locality", "item_number", "item_part"] def catalog_number(self): """ Generate a pretty string formatted catalog number from constituent fields :return: catalog number as string """ if self.basis_of_record == 'Collection': # Crate catalog number string. Null values become None when converted to string if self.item_number: if self.item_part: item_text = '-' + str(self.item_number) + str(self.item_part) else: item_text = '-' + str(self.item_number) else: item_text = '' catalog_number_string = str(self.collection_code) + " " + str(self.locality_id) + item_text return catalog_number_string.replace('None', '').replace('- ', '') # replace None with empty string else: return None @staticmethod def fields_to_display(): fields = ("id", "barcode") return fields @staticmethod def method_fields_to_export(): """ Method to store a list of fields that should be added to data exports. Called by export admin actions. These fields are defined in methods and are not concrete fields in the DB so have to be declared. :return: """ return ['longitude', 'latitude', 'easting', 'northing', 'catalog_number', 'photo'] def get_all_field_names(self): """ Field names from model :return: list with all field names """ field_list = self._meta.get_fields() # produce a list of field objects return [f.name for f in field_list] # return a list of names from each field def get_foreign_key_field_names(self): """ Get foreign key fields :return: returns a list of for key field names """ field_list = self._meta.get_fields() # produce a list of field objects return [f.name for f in field_list if f.is_relation] # return a list of names for fk fields def get_concrete_field_names(self): """ Get field names that correspond to columns in the DB :return: returns a lift """ field_list = self._meta.get_fields() return [f.name for f in field_list if f.concrete]
class Attachment(CreatedUpdatedModel): created_by = models.EmailField(null=True, blank=True) _signal = models.ForeignKey( "signals.Signal", null=False, on_delete=models.CASCADE, related_name='attachments', ) file = models.FileField(upload_to='attachments/%Y/%m/%d/', null=False, blank=False, max_length=255) mimetype = models.CharField(max_length=30, blank=False, null=False) is_image = models.BooleanField(default=False) class Meta: ordering = ('created_at', ) indexes = [ models.Index(fields=['created_at']), models.Index(fields=['is_image']), models.Index(fields=['_signal', 'is_image']), ] class NotAnImageException(Exception): pass class CroppedImage(ImageSpec): processors = [ ResizeToFit(800, 800), ] format = 'JPEG' options = {'quality': 80} @property def image_crop(self): return self._crop_image() def _crop_image(self): if not self.is_image: raise Attachment.NotAnImageException( "Attachment is not an image. Use is_image to check" " if attachment is an image before asking for the " "cropped version.") generator = Attachment.CroppedImage(source=self.file) cache_file = ImageCacheFile(generator) try: cache_file.generate() except FileNotFoundError as e: logger.warn("File not found when generating cache file: " + str(e)) return cache_file def save(self, *args, **kwargs): if self.pk is None: # Check if file is image self.is_image = imghdr.what(self.file) is not None if not self.mimetype and hasattr(self.file.file, 'content_type'): self.mimetype = self.file.file.content_type super().save(*args, **kwargs)
class DiscountCoupon(models.Model): sandwich_model = CouponSandwich coupon_type = models.ForeignKey( DiscountCouponType, verbose_name=_("typ voucheru"), null=False, blank=False, default='', on_delete=models.CASCADE, ) token = models.TextField( verbose_name=_("token"), blank=False, null=False, unique=True, ) discount = models.PositiveIntegerField( verbose_name=_("sleva (v procentech)"), null=False, blank=False, default=100, validators=[MaxValueValidator(100)], ) user_attendance_number = models.PositiveIntegerField( verbose_name=_("Počet možných využití"), help_text=_("Pokud se nevyplní, bude počet využití neomezený"), null=True, blank=True, default=1, ) note = models.CharField( verbose_name=_("poznámka"), max_length=50, blank=True, null=True, ) receiver = models.CharField( verbose_name=_("příjemce"), max_length=50, blank=True, null=True, ) created = models.DateTimeField( verbose_name=_(u"Datum vytvoření"), auto_now_add=True, null=True, ) updated = models.DateTimeField( verbose_name=_(u"Datum poslední změny"), auto_now=True, null=True, ) sent = models.BooleanField( verbose_name=_("DEPRECATED"), default=False, null=False, ) coupon_pdf = models.FileField( verbose_name=_(u"DEPRECATED"), upload_to='coupons', blank=True, null=True, ) def get_pdf(self): try: url = self.couponsandwich_set.get().pdf.url except (CouponSandwich.DoesNotExist, ValueError): try: url = self.coupon_pdf.url except ValueError: url = None if url: return format_html("<a href='{}'>{}</a>", url, _('PDF file')) else: return '-' get_pdf.short_description = _("PDF") def available(self): if self.user_attendance_number is None: return True user_count = self.userattendance_set.count() return self.user_attendance_number > user_count def get_sandwich_type(self): return self.coupon_type.sandwich_type class Meta: verbose_name = _("Slevový kupón") verbose_name_plural = _("Slevové kupóny") unique_together = ( ("token", "coupon_type"), ) app_label = "coupons" def __str__(self): return "%s-%s" % (self.coupon_type.prefix, self.token) def discount_multiplier(self): return (100 - self.discount) / 100.0 def name(self): return self.__str__() def attached_user_attendances_list(self): return ", ".join([str(u) for u in self.userattendance_set.all()]) def attached_user_attendances_count(self): return self.userattendance_set.count() def save(self, *args, **kwargs): if self.token is None or self.token == "": self.token = User.objects.make_random_password(length=6, allowed_chars='ABCDEFGHJKLMNPQRSTUVWXYZ') super().save(*args, **kwargs)