示例#1
0
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
示例#2
0
class AuditPhrase(models.Model):
    phrase = models.CharField(max_length=200)
    active = models.BooleanField(default=True)

    def __unicode__(self):
        return self.phrase
示例#3
0
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__())
示例#4
0
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
示例#5
0
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))
示例#6
0
文件: models.py 项目: acdh-oeaw/iad
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)
示例#7
0
文件: models.py 项目: edgbaez/ayudapy
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}>"
示例#8
0
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()
示例#9
0
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)
示例#10
0
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)
示例#11
0
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)
示例#12
0
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)
示例#13
0
文件: license.py 项目: chakibBH/idgo
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
示例#14
0
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)
示例#15
0
class Classroom(models.Model):
    students = models.ManyToManyField(Person, null=True)
    active = models.BooleanField(null=True)
示例#16
0
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')
示例#17
0
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()
示例#18
0
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
示例#19
0
文件: models.py 项目: acdh-oeaw/iad
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
示例#20
0
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
示例#21
0
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__())
示例#22
0
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
示例#23
0
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')
示例#24
0
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'
示例#25
0
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")
示例#26
0
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"
示例#27
0
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
示例#28
0
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]
示例#29
0
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)