Exemplo n.º 1
0
class TouristicEvent(AddPropertyMixin, PublishableMixin, MapEntityMixin,
                     StructureRelated, PicturesMixin, TimeStampedModelMixin,
                     NoDeleteMixin):
    """ A touristic event (conference, workshop, etc.) in the park
    """
    description_teaser = models.TextField(verbose_name=_("Description teaser"),
                                          blank=True,
                                          help_text=_("A brief summary"),
                                          db_column='chapeau')
    description = models.TextField(verbose_name=_("Description"),
                                   blank=True,
                                   db_column='description',
                                   help_text=_("Complete description"))
    themes = models.ManyToManyField(Theme,
                                    related_name="touristic_events",
                                    db_table="t_r_evenement_touristique_theme",
                                    blank=True,
                                    verbose_name=_("Themes"),
                                    help_text=_("Main theme(s)"))
    geom = models.PointField(verbose_name=_("Location"), srid=settings.SRID)
    begin_date = models.DateField(blank=True,
                                  null=True,
                                  verbose_name=_("Begin date"),
                                  db_column='date_debut')
    end_date = models.DateField(blank=True,
                                null=True,
                                verbose_name=_("End date"),
                                db_column='date_fin')
    duration = models.CharField(verbose_name=_("Duration"),
                                max_length=64,
                                blank=True,
                                db_column='duree',
                                help_text=_("3 days, season, ..."))
    meeting_point = models.CharField(verbose_name=_("Meeting point"),
                                     max_length=256,
                                     blank=True,
                                     db_column='point_rdv',
                                     help_text=_("Where exactly ?"))
    meeting_time = models.TimeField(verbose_name=_("Meeting time"),
                                    blank=True,
                                    null=True,
                                    db_column='heure_rdv',
                                    help_text=_("11:00, 23:30"))
    contact = models.TextField(verbose_name=_("Contact"),
                               blank=True,
                               db_column='contact')
    email = models.EmailField(verbose_name=_("Email"),
                              max_length=256,
                              db_column='email',
                              blank=True,
                              null=True)
    website = models.URLField(verbose_name=_("Website"),
                              max_length=256,
                              db_column='website',
                              blank=True,
                              null=True)
    organizer = models.CharField(verbose_name=_("Organizer"),
                                 max_length=256,
                                 blank=True,
                                 db_column='organisateur')
    speaker = models.CharField(verbose_name=_("Speaker"),
                               max_length=256,
                               blank=True,
                               db_column='intervenant')
    type = models.ForeignKey(TouristicEventType,
                             verbose_name=_("Type"),
                             blank=True,
                             null=True,
                             db_column='type')
    accessibility = models.CharField(verbose_name=_("Accessibility"),
                                     max_length=256,
                                     blank=True,
                                     db_column='accessibilite')
    participant_number = models.CharField(
        verbose_name=_("Number of participants"),
        max_length=256,
        blank=True,
        db_column='nb_places')
    booking = models.TextField(verbose_name=_("Booking"),
                               blank=True,
                               db_column='reservation')
    target_audience = models.CharField(verbose_name=_("Target audience"),
                                       max_length=128,
                                       blank=True,
                                       null=True,
                                       db_column='public_vise')
    practical_info = models.TextField(
        verbose_name=_("Practical info"),
        blank=True,
        db_column='infos_pratiques',
        help_text=_("Recommandations / To plan / Advices"))
    source = models.ManyToManyField(
        'common.RecordSource',
        blank=True,
        related_name='touristicevents',
        verbose_name=_("Source"),
        db_table='t_r_evenement_touristique_source')
    portal = models.ManyToManyField(
        'common.TargetPortal',
        blank=True,
        related_name='touristicevents',
        verbose_name=_("Portal"),
        db_table='t_r_evenement_touristique_portal')
    eid = models.CharField(verbose_name=_("External id"),
                           max_length=1024,
                           blank=True,
                           null=True,
                           db_column='id_externe')
    approved = models.BooleanField(verbose_name=_("Approved"),
                                   default=False,
                                   db_column='labellise')

    objects = NoDeleteMixin.get_manager_cls(models.GeoManager)()

    category_id_prefix = 'E'

    class Meta:
        db_table = 't_t_evenement_touristique'
        verbose_name = _("Touristic event")
        verbose_name_plural = _("Touristic events")
        ordering = ['-begin_date']

    def __str__(self):
        return self.name

    @property
    def type1(self):
        return [self.type] if self.type else []

    @property
    def type2(self):
        return []

    @property
    def districts_display(self):
        return ', '.join([str(d) for d in self.districts])

    @property
    def dates_display(self):
        if not self.begin_date and not self.end_date:
            return ""
        elif not self.end_date:
            return _("starting from {begin}").format(
                begin=date_format(self.begin_date, 'SHORT_DATE_FORMAT'))
        elif not self.begin_date:
            return _("up to {end}").format(
                end=date_format(self.end_date, 'SHORT_DATE_FORMAT'))
        elif self.begin_date == self.end_date:
            return date_format(self.begin_date, 'SHORT_DATE_FORMAT')
        else:
            return _("from {begin} to {end}").format(
                begin=date_format(self.begin_date, 'SHORT_DATE_FORMAT'),
                end=date_format(self.end_date, 'SHORT_DATE_FORMAT'))

    @property
    def prefixed_category_id(self):
        return self.category_id_prefix

    def distance(self, to_cls):
        return settings.TOURISM_INTERSECTION_MARGIN

    @property
    def portal_display(self):
        return ', '.join([str(portal) for portal in self.portal.all()])

    @property
    def source_display(self):
        return ', '.join([str(source) for source in self.source.all()])

    @property
    def themes_display(self):
        return ','.join([str(source) for source in self.themes.all()])

    @property
    def rando_url(self):
        category_slug = _('touristic-event')
        return '{}/{}/'.format(category_slug, self.slug)

    @property
    def meta_description(self):
        return plain_text(self.description_teaser or self.description)[:500]
Exemplo n.º 2
0
class Unit(ModifiableModel, AutoIdentifiedModel):
    id = models.CharField(primary_key=True, max_length=50)
    name = models.CharField(verbose_name=_('Name'), max_length=200)
    description = models.TextField(verbose_name=_('Description'),
                                   null=True,
                                   blank=True)

    location = models.PointField(verbose_name=_('Location'),
                                 null=True,
                                 srid=settings.DEFAULT_SRID)
    time_zone = models.CharField(verbose_name=_('Time zone'),
                                 max_length=50,
                                 default=timezone.get_default_timezone().zone,
                                 choices=[(x, x) for x in pytz.all_timezones])

    # organization = models.ForeignKey(...)
    street_address = models.CharField(verbose_name=_('Street address'),
                                      max_length=100,
                                      null=True)
    address_zip = models.CharField(verbose_name=_('Postal code'),
                                   max_length=10,
                                   null=True,
                                   blank=True)
    phone = models.CharField(verbose_name=_('Phone number'),
                             max_length=30,
                             null=True,
                             blank=True)
    email = models.EmailField(verbose_name=_('Email'),
                              max_length=100,
                              null=True,
                              blank=True)
    www_url = models.URLField(verbose_name=_('WWW link'),
                              max_length=400,
                              null=True,
                              blank=True)
    address_postal_full = models.CharField(
        verbose_name=_('Full postal address'),
        max_length=100,
        null=True,
        blank=True)
    municipality = models.ForeignKey(Municipality,
                                     null=True,
                                     blank=True,
                                     verbose_name=_('Municipality'))

    picture_url = models.URLField(verbose_name=_('Picture URL'),
                                  max_length=200,
                                  null=True,
                                  blank=True)
    picture_caption = models.CharField(verbose_name=_('Picture caption'),
                                       max_length=200,
                                       null=True,
                                       blank=True)

    slug = AutoSlugField(populate_from=get_translated_name, unique=True)
    reservable_days_in_advance = models.PositiveSmallIntegerField(
        verbose_name=_('Reservable days in advance'), null=True, blank=True)

    class Meta:
        verbose_name = _("unit")
        verbose_name_plural = _("units")
        permissions = (
            ('can_approve_reservation', _('Can approve reservation')),
            ('can_view_reservation_access_code',
             _('Can view reservation access code')),
        )
        ordering = ('name', )

    def __str__(self):
        return "%s (%s)" % (get_translated(self, 'name'), self.id)

    def get_opening_hours(self, begin=None, end=None):
        """
        :rtype : dict[str, list[dict[str, datetime.datetime]]]
        :type begin: datetime.date
        :type end: datetime.date
        """
        return get_opening_hours(self.time_zone, self.periods, begin, end)

    def get_tz(self):
        return pytz.timezone(self.time_zone)

    def get_reservable_before(self):
        return create_reservable_before_datetime(
            self.reservable_days_in_advance)

    def is_admin(self, user):
        # Currently all staff members are allowed to administrate
        # all units. Might be more finegrained in the future.
        return user.is_staff
class Contributor(models.Model):
    zip_code = models.CharField(max_length=5)
    email = models.EmailField(null=True)
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
Exemplo n.º 4
0
class Place(models.Model):
    name = models.TextField()
    place_id = models.TextField(primary_key=True)
    lat = models.FloatField()
    lng = models.FloatField()
    user_rating = models.FloatField()
    num_ratings = models.FloatField()
    address = models.TextField()
    area = models.ForeignKey(to='Area',
                             null=True,
                             blank=True,
                             on_delete=models.SET_NULL)
    email_contact = models.EmailField(null=True, blank=True)
    place_url = models.URLField(null=True, blank=True, max_length=1000)
    image_url = models.URLField(null=True, blank=True, max_length=1000)
    image_attribution = models.TextField(null=True, blank=True)
    gift_card_url = models.URLField(null=True, blank=True, max_length=1000)
    takeout_url = models.URLField(null=True, blank=True, max_length=1000)
    donation_url = models.URLField(null=True, blank=True, max_length=1000)
    geom = models.PointField(srid=4326, null=True, blank=True)
    place_types = models.TextField(null=True, blank=True)

    @classmethod
    def dump_names_for_site(cls, out_fl):
        all_places = cls.objects.all()
        output = []
        for place in all_places:
            info = (
                """{{key: "{place_id}", address: "{address}", name: "{name}"}},"""
                .format(name=place.name,
                        address=place.get_short_address(),
                        place_id=place.place_id))
            output.append(info)
        with open(out_fl, 'w') as fl:
            fl.writelines(output)

    @classmethod
    def dump_places_missing_photos(cls, out_fl):
        missing_photo = cls.objects.filter(image_url=None)
        names = ['%s\n' % place.place_id for place in missing_photo]
        with open(out_fl, 'w') as fl:
            fl.writelines(names)

    @classmethod
    def dump_places_missing_website(cls, out_fl):
        missing_photo = cls.objects.filter(place_url=None)
        names = ['%s\n' % place.place_id for place in missing_photo]
        with open(out_fl, 'w') as fl:
            fl.writelines(names)

    def get_image_url(self):
        return self.image_url or "http://TODO/placeholder"

    def get_short_address(self):
        return self.address.split(', CA')[0]

    def to_json(self):
        return {
            'name': self.name,
            'address': self.get_short_address(),
            'giftCardURL': self.gift_card_url,
            'takeoutURL': self.takeout_url,
            'donationURL': self.donation_url,
            'placeURL': self.place_url,
            'emailContact': self.email_contact,
            'imageURL': self.get_image_url(),
            'placeID': self.place_id,
            'area': self.area.key if self.area else None
        }

    def to_typeahead_json(self):
        return {
            'name': self.name,
            'address': self.get_short_address(),
            'key': self.place_id,
            'image_attribution': self.image_attribution
        }

    def __str__(self):
        return '%s (%s)' % (self.name, self.address)

    def save(self, *args, **kwargs):
        from places.helper import check_link_against_blacklist
        if self.gift_card_url and not check_link_against_blacklist(
                self.gift_card_url):
            raise Exception("Bad Link Saved")
        if (self.lat and self.lng):
            self.geom = Point([float(x) for x in (self.lng, self.lat)],
                              srid=4326)
        super(self.__class__, self).save(*args, **kwargs)
Exemplo n.º 5
0
class CitacionsEspecie(models.Model):
    id = models.AutoField(primary_key=True)
    especie = models.CharField(max_length=255, blank=True, null=True)
    # idspinvasora = models.ForeignKey(Especieinvasora, null=True) # FOREIGN
    #idspinvasora = models.CharField(max_length=255, blank=True, null=True)
    idspinvasora = models.ForeignKey(Especieinvasora,
                                     models.DO_NOTHING,
                                     db_column='idspinvasora',
                                     blank=True,
                                     null=True)
    data = models.CharField(max_length=100, blank=True, null=True)

    comarca = models.CharField(max_length=100, blank=True, null=True)
    municipi = models.CharField(max_length=255, blank=True, null=True)
    localitat = models.CharField(max_length=255, blank=True, null=True)
    finca = models.CharField(max_length=255, blank=True, null=True)
    paratge = models.CharField(max_length=255, blank=True, null=True)
    utmx = models.FloatField(blank=True, null=True)
    utmy = models.FloatField(blank=True, null=True)
    utmz = models.FloatField(blank=True, null=True)
    utm_10 = models.CharField(max_length=4, blank=True, null=True)  #ej DG89
    utm_1 = models.CharField(max_length=6, blank=True, null=True)  #ej DG8391
    geom = models.TextField(blank=True, null=True)
    geom_4326 = models.TextField(blank=True, null=True)
    #geom_4326 = models.GeometryField(blank=True, null=True) # se usara el de arriba para mostrar los datos en el mapa

    propietari_nom = models.CharField(max_length=255, blank=True, null=True)
    adreca = models.CharField(max_length=255, blank=True, null=True)
    poblacio = models.CharField(max_length=255, blank=True, null=True)
    telefon = models.CharField(max_length=255, blank=True, null=True)

    qual_terreny = models.CharField(max_length=255, blank=True, null=True)

    espai_natural_protegit = models.CharField(max_length=255,
                                              blank=True,
                                              null=True)
    espai_nom = models.CharField(max_length=255, blank=True, null=True)

    superficie_ocupada = models.CharField(max_length=100,
                                          blank=True,
                                          null=True)

    presencia = models.CharField(max_length=255, blank=True, null=True)

    estat_invasio = models.CharField(max_length=255, blank=True,
                                     null=True)  # foreign de estatus?

    observacions = models.CharField(max_length=4000, blank=True, null=True)

    grup = models.CharField(max_length=255, blank=True, null=True)

    # imatges = models.FileField(upload_to='imatges',blank=True,null=True)

    # imatge_panoramica = models.ForeignKey('Imatges', models.DO_NOTHING, related_name='citacio_imatge_panoramica', blank=True, null=True)
    # imatge_detall_espcie = models.ForeignKey('Imatges', models.DO_NOTHING, related_name='citacio_imatge_detall_especie', blank=True, null=True)
    # imatge_detall_localitat_1 = models.ForeignKey('Imatges', models.DO_NOTHING, related_name='citacio_imatge_detall_localitat_1',blank=True, null=True)
    # imatge_detall_localitat_2 = models.ForeignKey('Imatges', models.DO_NOTHING,related_name='citacio_imatge_detall_localitat_2', blank=True,null=True)

    contacte = models.EmailField(blank=True, null=True)
    NIP = models.CharField(max_length=255, blank=True, null=True)
    validat = models.CharField(max_length=255, blank=True, null=True)

    usuari = models.CharField(max_length=255, blank=True,
                              null=True)  # usuario que ha creado el formulario
    data_creacio = models.CharField(max_length=100, blank=True, null=True)
    data_modificacio = models.CharField(max_length=100, blank=True, null=True)

    class Meta:
        managed = True
        db_table = 'citacions_especie'
Exemplo n.º 6
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()
Exemplo n.º 7
0
class App(models.Model):
    name = models.CharField(max_length=200, null=True, blank=True)
    title = models.CharField(max_length=200, null=True, blank=True)
    description = models.TextField(null=True, blank=True)
    short_description = models.TextField(null=True, blank=True)
    app_url = models.URLField(null=True, blank=True)
    author = models.CharField(max_length=200, null=True, blank=True)
    author_website = models.URLField(null=True, blank=True)
    license = models.CharField(max_length=200, null=True, blank=True)
    tags = TaggableManager()
    date_installed = models.DateTimeField(
        'Date Installed', auto_now_add=True, null=True)
    installed_by = models.ForeignKey(
        geonode_settings.AUTH_USER_MODEL, null=True, blank=True)
    single_instance = models.BooleanField(
        default=False, null=False, blank=False)
    category = models.ManyToManyField(AppType, related_name='apps')
    status = models.CharField(
        max_length=100, blank=False, null=False, default='Alpha')
    owner_url = models.URLField(null=True, blank=True)
    help_url = models.URLField(null=True, blank=True)
    app_img_url = models.TextField(max_length=1000, blank=True, null=True)
    rating = models.IntegerField(default=0, null=True, blank=True)
    contact_name = models.CharField(max_length=200, null=True, blank=True)
    contact_email = models.EmailField(null=True, blank=True)
    version = models.CharField(max_length=10)
    store = models.ForeignKey(AppStore, null=True)
    order = models.IntegerField(null=True, default=0)

    default_config = JSONField(default={})

    class meta(object):
        ordering = ['order']

    def __str__(self):
        return self.title

    @property
    def settings_url(self):
        try:
            return reverse("%s_settings" % self.name)
        except BaseException as e:
            logger.error(e.message)
            return None

    @property
    def urls(self):
        admin_urls = logged_in_urls = anonymous_urls = None
        try:
            app_module = __import__(self.name)
            if hasattr(app_module, 'urls_dict'):
                urls_dict = getattr(app_module, 'urls_dict')
                if 'admin' in list(urls_dict.keys()):
                    admin_urls = urls_dict['admin']
                else:
                    admin_urls = None
                if 'logged_in' in list(urls_dict.keys()):
                    logged_in_urls = urls_dict['logged_in']
                else:
                    logged_in_urls = None
                if 'anonymous' in list(urls_dict.keys()):
                    anonymous_urls = urls_dict['anonymous']
                else:
                    anonymous_urls = None
        except ImportError as e:
            logger.error(e.message)
        return (admin_urls, logged_in_urls, anonymous_urls)

    @property
    def open_url(self):
        from django.core.urlresolvers import reverse
        open_url = reverse('app_manager_base_url') + self.name
        try:
            app_module = __import__(self.name)
            if hasattr(app_module, 'OPEN_URL_NAME'):
                open_url = reverse(getattr(app_module, 'OPEN_URL_NAME'))
        except ImportError as e:
            logger.error(e.message)
        return open_url

    @property
    def create_new_url(self):
        from django.core.urlresolvers import reverse
        create_new_url = reverse('{}.new'.format(self.name))
        try:
            app_module = __import__(self.name)
            if hasattr(app_module, 'CREATE_NEW_URL_NAME'):
                create_new_url = reverse(
                    getattr(app_module, 'CREATE_NEW_URL_NAME'))
        except ImportError as e:
            logger.error(e.message)
        return create_new_url

    @property
    def admin_urls(self):
        return self.urls[0]

    @property
    def logged_in_urls(self):
        return self.urls[1]

    @property
    def anonymous_urls(self):
        return self.urls[2]

    @property
    def new_url(self):
        try:
            return reverse("%s.new" % self.name)
        except BaseException as e:
            logger.error(e.message)
            return None

    def set_active(self, active=True):
        app = CartoviewApp.objects.get(self.name, None)
        if app:
            app.active = active
            app.commit()
            CartoviewApp.save()
        return app

    @property
    def config(self):
        return CartoviewApp.objects.get(self.name, None)
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='')
    contact_email = models.EmailField(blank=True,
                                      default='',
                                      verbose_name=_('Contact email'))
    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,
    )
    community_skills = models.TextField(
        blank=True, default='')  # Only applies to Community Builders.
    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.
    affiliation_url = models.URLField(blank=True, default='', max_length=255)
    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('mdi.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'
Exemplo n.º 9
0
class Status(CreatedUpdatedModel):
    TARGET_API_SIGMAX = 'sigmax'
    TARGET_API_CHOICES = ((TARGET_API_SIGMAX, 'Sigmax (City Control)'), )

    _signal = models.ForeignKey('signals.Signal',
                                related_name='statuses',
                                on_delete=models.CASCADE)

    text = models.CharField(max_length=10000, null=True, blank=True)
    # TODO, rename field to `email` it's not a `User` it's a `email`...
    user = models.EmailField(null=True, blank=True)
    target_api = models.CharField(max_length=250,
                                  null=True,
                                  blank=True,
                                  choices=TARGET_API_CHOICES)
    state = models.CharField(max_length=20,
                             blank=True,
                             choices=workflow.STATUS_CHOICES,
                             default=workflow.GEMELD,
                             help_text='Melding status')

    # TODO, do we need this field or can we remove it?
    extern = models.BooleanField(
        default=False, help_text='Wel of niet status extern weergeven')

    extra_properties = models.JSONField(null=True, blank=True)

    # SIG-2620 Flag to determine if a status can send an email
    send_email = models.BooleanField(default=False)

    history_log = GenericRelation('history.Log', object_id_field='object_pk')

    class Meta:
        permissions = (
            ('push_to_sigmax',
             'Doorsturen van een melding (THOR)'),  # SIG-2192
        )
        verbose_name_plural = 'Statuses'
        get_latest_by = 'datetime'
        ordering = ('created_at', )

    def __str__(self):
        return str(self.text)

    # TODO: Maybe migrate user to created_by, for now made this work-around
    @property
    def created_by(self):
        return self.user

    @created_by.setter
    def created_by(self, created_by):
        self.user = created_by

    def clean(self):
        """Validate instance.

        Most important validation is the state transition.

        :raises: ValidationError
        :returns:
        """
        errors = {}

        if self._signal.status:
            # We already have a status so let's check if the new status can be set
            current_state = self._signal.status.state
            current_state_display = self._signal.status.get_state_display()
        else:
            # No status has been set yet so we default to LEEG
            current_state = workflow.LEEG
            current_state_display = workflow.LEEG

            logger.warning('Signal #{} has status set to None'.format(
                self._signal.pk))

        new_state = self.state
        new_state_display = self.get_state_display()

        # Validating state transition.
        if new_state not in workflow.ALLOWED_STATUS_CHANGES[current_state]:
            error_msg = 'Invalid state transition from `{from_state}` to `{to_state}`.'.format(
                from_state=current_state_display, to_state=new_state_display)
            errors['state'] = ValidationError(error_msg, code='invalid')

        # Validating state "TE_VERZENDEN".
        if new_state == workflow.TE_VERZENDEN and not self.target_api:
            error_msg = 'This field is required when changing `state` to `{new_state}`.'.format(
                new_state=new_state_display)
            errors['target_api'] = ValidationError(error_msg, code='required')

        if new_state != workflow.TE_VERZENDEN and self.target_api:
            error_msg = 'This field can only be set when changing `state` to `{state}`.'.format(
                state=workflow.TE_VERZENDEN)
            errors['target_api'] = ValidationError(error_msg, code='invalid')

        # Validating text field required.
        if new_state in [workflow.AFGEHANDELD, workflow.HEROPEND
                         ] and not self.text:
            error_msg = 'This field is required when changing `state` to `{new_state}`.'.format(
                new_state=new_state_display)
            errors['text'] = ValidationError(error_msg, code='required')

        if errors:
            raise ValidationError(errors)
Exemplo n.º 10
0
Arquivo: unit.py Projeto: jussih/respa
class Unit(ModifiableModel, AutoIdentifiedModel):
    id = models.CharField(primary_key=True, max_length=50)
    name = models.CharField(verbose_name=_('Name'), max_length=200)
    description = models.TextField(verbose_name=_('Description'), null=True, blank=True)

    location = models.PointField(verbose_name=_('Location'), null=True, srid=settings.DEFAULT_SRID)
    time_zone = models.CharField(verbose_name=_('Time zone'), max_length=50,
                                 default=_get_default_timezone)

    manager_email = models.EmailField(verbose_name=_('Manager email'), max_length=100, null=True, blank=True)

    street_address = models.CharField(verbose_name=_('Street address'), max_length=100, null=True)
    address_zip = models.CharField(verbose_name=_('Postal code'), max_length=10, null=True, blank=True)
    phone = models.CharField(verbose_name=_('Phone number'), max_length=30, null=True, blank=True)
    email = models.EmailField(verbose_name=_('Email'), max_length=100, null=True, blank=True)
    www_url = models.URLField(verbose_name=_('WWW link'), max_length=400, null=True, blank=True)
    address_postal_full = models.CharField(verbose_name=_('Full postal address'), max_length=100,
                                           null=True, blank=True)
    municipality = models.ForeignKey(Municipality, null=True, blank=True, verbose_name=_('Municipality'),
                                     on_delete=models.SET_NULL)

    picture_url = models.URLField(verbose_name=_('Picture URL'), max_length=200,
                                  null=True, blank=True)
    picture_caption = models.CharField(verbose_name=_('Picture caption'), max_length=200,
                                       null=True, blank=True)

    reservable_max_days_in_advance = models.PositiveSmallIntegerField(verbose_name=_('Reservable max. days in advance'),
                                                                      null=True, blank=True)
    reservable_min_days_in_advance = models.PositiveSmallIntegerField(verbose_name=_('Reservable min. days in advance'),
                                                                      null=True, blank=True)
    data_source = models.CharField(max_length=128, blank=True, default='',
                                   verbose_name=_('External data source'))
    data_source_hours = models.CharField(max_length=128, blank=True, default='',
                                         verbose_name=_('External data source for opening hours'))

    objects = UnitQuerySet.as_manager()

    class Meta:
        verbose_name = _("unit")
        verbose_name_plural = _("units")
        permissions = UNIT_PERMISSIONS
        ordering = ('name',)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Set the time zone choices here in order to avoid spawning
        # spurious migrations.
        self._meta.get_field('time_zone').choices = _get_timezone_choices()

    def __str__(self):
        return "%s (%s)" % (get_translated(self, 'name'), self.id)

    def get_opening_hours(self, begin=None, end=None):
        """
        :rtype : dict[str, list[dict[str, datetime.datetime]]]
        :type begin: datetime.date
        :type end: datetime.date
        """
        return get_opening_hours(self.time_zone, list(self.periods.all()), begin, end)

    def update_opening_hours(self):
        for res in self.resources.all():
            res.update_opening_hours()

    def get_tz(self):
        return pytz.timezone(self.time_zone)

    def get_reservable_before(self):
        return create_datetime_days_from_now(self.reservable_max_days_in_advance)

    def get_reservable_after(self):
        return create_datetime_days_from_now(self.reservable_min_days_in_advance)

    def is_admin(self, user):
        return is_authenticated_user(user) and (
            is_general_admin(user) or
            user.unit_authorizations.to_unit(self).admin_level().exists() or
            (user.unit_group_authorizations
             .to_unit(self).admin_level().exists()))

    def is_manager(self, user):
        return self.is_admin(user) or (is_authenticated_user(user) and (
            user.unit_authorizations.to_unit(self).manager_level().exists()))

    def has_imported_data(self):
        return self.data_source != ''

    def has_imported_hours(self):
        return self.data_source_hours != ''

    def is_editable(self):
        """ Whether unit is editable by normal admin users or not """
        return not (self.has_imported_data() or self.has_imported_hours())
Exemplo n.º 11
0
class AuthUser(AbstractBaseUser, PermissionsMixin):
    account = models.ForeignKey(Account,
                                null=True,
                                blank=True,
                                related_name='users')
    email = models.EmailField(
        _('email address'),
        unique=True,
        help_text=_('Required.'),
        error_messages={
            'unique': _('The given email address has already been registered.')
        })
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    metadata = models.TextField(blank=True)

    is_account_admin = models.BooleanField(
        _('account admin status'),
        default=False,
        help_text=_('Designates whether the user is as account admin.'))

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into the admin site.'))
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_('Designates whether this user should be treated as '
                    'active. Unselect this instead of deleting accounts.'))
    created_at = models.DateTimeField(_('created at'), editable=False)
    updated_at = models.DateTimeField(_('updated at'), editable=False)

    objects = AuthUserManager()

    USERNAME_FIELD = 'email'

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        return self.first_name

    def get_token(self):
        try:
            token = Token.objects.get(user=self)
        except Token.DoesNotExist:
            token = ''
        return str(token)

    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)

    def save(self, *args, **kwargs):
        now = datetime.now(timezone.utc)
        if not self.id:
            self.created_at = now
            self.updated_at = now
            if self.account is not None and len(
                    AuthUser.objects.filter(account=self.account,
                                            is_account_admin=True)) == 0:
                self.is_account_admin = True  # an account needs an account admin, so make it the first user to enroll
        else:
            self.updated_at = now
        super(AuthUser, self).save(*args, **kwargs)
Exemplo n.º 12
0
class Person(models.Model):
    name = models.CharField(max_length=130)
    email = models.EmailField(blank=True)
    job_title = models.CharField(max_length=30, blank=True)
    bio = models.TextField(blank=True)
Exemplo n.º 13
0
class Reservation(ModifiableModel):
    CREATED = 'created'
    CANCELLED = 'cancelled'
    CONFIRMED = 'confirmed'
    DENIED = 'denied'
    REQUESTED = 'requested'
    STATE_CHOICES = (
        (CREATED, _('created')),
        (CANCELLED, _('cancelled')),
        (CONFIRMED, _('confirmed')),
        (DENIED, _('denied')),
        (REQUESTED, _('requested')),
    )

    resource = models.ForeignKey('Resource',
                                 verbose_name=_('Resource'),
                                 db_index=True,
                                 related_name='reservations',
                                 on_delete=models.PROTECT)
    begin = models.DateTimeField(verbose_name=_('Begin time'))
    end = models.DateTimeField(verbose_name=_('End time'))
    duration = pgfields.DateTimeRangeField(
        verbose_name=_('Length of reservation'),
        null=True,
        blank=True,
        db_index=True)
    comments = models.TextField(null=True,
                                blank=True,
                                verbose_name=_('Comments'))
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             verbose_name=_('User'),
                             null=True,
                             blank=True,
                             db_index=True,
                             on_delete=models.PROTECT)
    state = models.CharField(max_length=16,
                             choices=STATE_CHOICES,
                             verbose_name=_('State'),
                             default=CONFIRMED)
    approver = models.ForeignKey(settings.AUTH_USER_MODEL,
                                 verbose_name=_('Approver'),
                                 related_name='approved_reservations',
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL)

    # access-related fields
    access_code = models.CharField(verbose_name=_('Access code'),
                                   max_length=32,
                                   null=True,
                                   blank=True)

    # EXTRA FIELDS START HERE

    event_subject = models.CharField(max_length=200,
                                     verbose_name=_('Event subject'),
                                     blank=True)
    event_description = models.TextField(verbose_name=_('Event description'),
                                         blank=True)
    number_of_participants = models.PositiveSmallIntegerField(
        verbose_name=_('Number of participants'), blank=True, null=True)
    participants = models.TextField(verbose_name=_('Participants'), blank=True)
    host_name = models.CharField(verbose_name=_('Host name'),
                                 max_length=100,
                                 blank=True)

    # extra detail fields for manually confirmed reservations
    reserver_name = models.CharField(verbose_name=_('Reserver name'),
                                     max_length=100,
                                     blank=True)
    reserver_id = models.CharField(
        verbose_name=_('Reserver ID (business or person)'),
        max_length=30,
        blank=True)
    reserver_email_address = models.EmailField(
        verbose_name=_('Reserver email address'), blank=True)
    reserver_phone_number = models.CharField(
        verbose_name=_('Reserver phone number'), max_length=30, blank=True)
    reserver_address_street = models.CharField(
        verbose_name=_('Reserver address street'), max_length=100, blank=True)
    reserver_address_zip = models.CharField(
        verbose_name=_('Reserver address zip'), max_length=30, blank=True)
    reserver_address_city = models.CharField(
        verbose_name=_('Reserver address city'), max_length=100, blank=True)
    company = models.CharField(verbose_name=_('Company'),
                               max_length=100,
                               blank=True)
    billing_address_street = models.CharField(
        verbose_name=_('Billing address street'), max_length=100, blank=True)
    billing_address_zip = models.CharField(
        verbose_name=_('Billing address zip'), max_length=30, blank=True)
    billing_address_city = models.CharField(
        verbose_name=_('Billing address city'), max_length=100, blank=True)

    # If the reservation was imported from another system, you can store the original ID in the field below.
    origin_id = models.CharField(verbose_name=_('Original ID'),
                                 max_length=50,
                                 editable=False,
                                 null=True)

    objects = ReservationQuerySet.as_manager()

    class Meta:
        verbose_name = _("reservation")
        verbose_name_plural = _("reservations")
        ordering = ('id', )

    def _save_dt(self, attr, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        save_dt(self, attr, dt, self.resource.unit.time_zone)

    def _get_dt(self, attr, tz):
        return get_dt(self, attr, tz)

    @property
    def begin_tz(self):
        return self.begin

    @begin_tz.setter
    def begin_tz(self, dt):
        self._save_dt('begin', dt)

    def get_begin_tz(self, tz):
        return self._get_dt("begin", tz)

    @property
    def end_tz(self):
        return self.end

    @end_tz.setter
    def end_tz(self, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        self._save_dt('end', dt)

    def get_end_tz(self, tz):
        return self._get_dt("end", tz)

    def is_active(self):
        return self.end >= timezone.now() and self.state not in (
            Reservation.CANCELLED, Reservation.DENIED)

    def is_own(self, user):
        if not (user and user.is_authenticated):
            return False
        return user == self.user

    def need_manual_confirmation(self):
        return self.resource.need_manual_confirmation

    def are_extra_fields_visible(self, user):
        # the following logic is used also implemented in ReservationQuerySet
        # so if this is changed that probably needs to be changed as well

        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_access_code(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_access_codes(user)

    def set_state(self, new_state, user):
        # Make sure it is a known state
        assert new_state in (Reservation.REQUESTED, Reservation.CONFIRMED,
                             Reservation.DENIED, Reservation.CANCELLED)

        old_state = self.state
        if new_state == old_state:
            if old_state == Reservation.CONFIRMED:
                reservation_modified.send(sender=self.__class__,
                                          instance=self,
                                          user=user)
            return

        if new_state == Reservation.CONFIRMED:
            self.approver = user
            reservation_confirmed.send(sender=self.__class__,
                                       instance=self,
                                       user=user)
        elif old_state == Reservation.CONFIRMED:
            self.approver = None

        # Notifications
        if new_state == Reservation.REQUESTED:
            self.send_reservation_requested_mail()
            self.send_reservation_requested_mail_to_officials()
        elif new_state == Reservation.CONFIRMED:
            if self.need_manual_confirmation():
                self.send_reservation_confirmed_mail()
            elif self.resource.is_access_code_enabled():
                self.send_reservation_created_with_access_code_mail()
        elif new_state == Reservation.DENIED:
            self.send_reservation_denied_mail()
        elif new_state == Reservation.CANCELLED:
            if user != self.user:
                self.send_reservation_cancelled_mail()
            reservation_cancelled.send(sender=self.__class__,
                                       instance=self,
                                       user=user)

        self.state = new_state
        self.save()

    def can_modify(self, user):
        if not user:
            return False

        # reservations that need manual confirmation and are confirmed cannot be
        # modified or cancelled without reservation approve permission
        cannot_approve = not self.resource.can_approve_reservations(user)
        if self.need_manual_confirmation(
        ) and self.state == Reservation.CONFIRMED and cannot_approve:
            return False

        return self.user == user or self.resource.can_modify_reservations(user)

    def can_add_comment(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_access_reservation_comments(user)

    def can_view_field(self, user, field):
        if field not in RESERVATION_EXTRA_FIELDS:
            return True
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_catering_orders(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_catering_orders(user)

    def format_time(self):
        tz = self.resource.unit.get_tz()
        begin = self.begin.astimezone(tz)
        end = self.end.astimezone(tz)
        return format_dt_range(translation.get_language(), begin, end)

    def __str__(self):
        if self.state != Reservation.CONFIRMED:
            state_str = ' (%s)' % self.state
        else:
            state_str = ''
        return "%s: %s%s" % (self.format_time(), self.resource, state_str)

    def clean(self, **kwargs):
        """
        Check restrictions that are common to all reservations.

        If this reservation isn't yet saved and it will modify an existing reservation,
        the original reservation need to be provided in kwargs as 'original_reservation', so
        that it can be excluded when checking if the resource is available.
        """
        if self.end <= self.begin:
            raise ValidationError(
                _("You must end the reservation after it has begun"))

        # Check that begin and end times are on valid time slots.
        opening_hours = self.resource.get_opening_hours(
            self.begin.date(), self.end.date())
        for dt in (self.begin, self.end):
            days = opening_hours.get(dt.date(), [])
            day = next((day for day in days if day['opens'] is not None
                        and day['opens'] <= dt <= day['closes']), None)
            if day and not is_valid_time_slot(dt, self.resource.min_period,
                                              day['opens']):
                raise ValidationError(
                    _("Begin and end time must match time slots"))

        original_reservation = self if self.pk else kwargs.get(
            'original_reservation', None)
        if self.resource.check_reservation_collision(self.begin, self.end,
                                                     original_reservation):
            raise ValidationError(
                _("The resource is already reserved for some of the period"))

        if (self.end - self.begin) < self.resource.min_period:
            raise ValidationError(
                _("The minimum reservation length is %(min_period)s") %
                {'min_period': humanize_duration(self.min_period)})

        if self.access_code:
            validate_access_code(self.access_code,
                                 self.resource.access_code_type)

    def get_notification_context(self, language_code, user=None):
        if not user:
            user = self.user
        with translation.override(language_code):
            reserver_name = self.reserver_name
            if not reserver_name and self.user and self.user.get_display_name(
            ):
                reserver_name = self.user.get_display_name()
            context = {
                'resource': self.resource.name,
                'begin': localize_datetime(self.begin),
                'end': localize_datetime(self.end),
                'begin_dt': self.begin,
                'end_dt': self.end,
                'time_range': self.format_time(),
                'number_of_participants': self.number_of_participants,
                'host_name': self.host_name,
                'reserver_name': reserver_name,
                'event_subject': self.event_subject,
            }
            if self.resource.unit:
                context['unit'] = self.resource.unit.name
            if self.can_view_access_code(user) and self.access_code:
                context['access_code'] = self.access_code
            if self.resource.reservation_confirmed_notification_extra:
                context[
                    'extra_content'] = self.resource.reservation_confirmed_notification_extra
        return context

    def send_reservation_mail(self, notification_type, user=None):
        """
        Stuff common to all reservation related mails.

        If user isn't given use self.user.
        """
        if user:
            email_address = user.email
        else:
            if not (self.reserver_email_address or self.user):
                return
            email_address = self.reserver_email_address or self.user.email
            user = self.user

        language = user.get_preferred_language() if user else DEFAULT_LANG
        context = self.get_notification_context(language)

        try:
            rendered_notification = render_notification_template(
                notification_type, context, language)
        except NotificationTemplateException as e:
            logger.error(e, exc_info=True, extra={'user': user.uuid})
            return

        send_respa_mail(email_address, rendered_notification['subject'],
                        rendered_notification['body'])

    def send_reservation_requested_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_REQUESTED)

    def send_reservation_requested_mail_to_officials(self):
        notify_users = self.resource.get_users_with_perm(
            'can_approve_reservation')
        if len(notify_users) > 100:
            raise Exception("Refusing to notify more than 100 users (%s)" %
                            self)
        for user in notify_users:
            self.send_reservation_mail(
                NotificationType.RESERVATION_REQUESTED_OFFICIAL, user=user)

    def send_reservation_denied_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_DENIED)

    def send_reservation_confirmed_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_CONFIRMED)

    def send_reservation_cancelled_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_CANCELLED)

    def send_reservation_created_with_access_code_mail(self):
        self.send_reservation_mail(
            NotificationType.RESERVATION_CREATED_WITH_ACCESS_CODE)

    def save(self, *args, **kwargs):
        self.duration = DateTimeTZRange(self.begin, self.end, '[)')

        access_code_type = self.resource.access_code_type
        if not self.resource.is_access_code_enabled():
            self.access_code = ''
        elif not self.access_code:
            self.access_code = generate_access_code(access_code_type)

        return super().save(*args, **kwargs)
Exemplo n.º 14
0
class InformationDesk(models.Model):

    name = models.CharField(verbose_name=_("Title"),
                            max_length=256,
                            db_column='nom')
    type = models.ForeignKey(InformationDeskType,
                             verbose_name=_("Type"),
                             related_name='desks',
                             db_column='type')
    description = models.TextField(verbose_name=_("Description"),
                                   blank=True,
                                   db_column='description',
                                   help_text=_("Brief description"))
    phone = models.CharField(verbose_name=_("Phone"),
                             max_length=32,
                             blank=True,
                             null=True,
                             db_column='telephone')
    email = models.EmailField(verbose_name=_("Email"),
                              max_length=256,
                              db_column='email',
                              blank=True,
                              null=True)
    website = models.URLField(verbose_name=_("Website"),
                              max_length=256,
                              db_column='website',
                              blank=True,
                              null=True)
    photo = models.FileField(verbose_name=_("Photo"),
                             upload_to=settings.UPLOAD_DIR,
                             db_column='photo',
                             max_length=512,
                             blank=True,
                             null=True)

    street = models.CharField(verbose_name=_("Street"),
                              max_length=256,
                              blank=True,
                              null=True,
                              db_column='rue')
    postal_code = models.CharField(verbose_name=_("Postal code"),
                                   max_length=8,
                                   blank=True,
                                   null=True,
                                   db_column='code')
    municipality = models.CharField(verbose_name=_("Municipality"),
                                    blank=True,
                                    null=True,
                                    max_length=256,
                                    db_column='commune')

    geom = models.PointField(verbose_name=_("Emplacement"),
                             db_column='geom',
                             blank=True,
                             null=True,
                             srid=settings.SRID,
                             spatial_index=False)

    objects = models.GeoManager()

    class Meta:
        db_table = 't_b_renseignement'
        verbose_name = _("Information desk")
        verbose_name_plural = _("Information desks")
        ordering = ['name']

    def __str__(self):
        return self.name

    @property
    def description_strip(self):
        """Used in trek public template.
        """
        nobr = re.compile(r'(\s*<br.*?>)+\s*', re.I)
        newlines = nobr.sub("\n", self.description)
        return smart_plain_text(newlines)

    @property
    def serializable_type(self):
        return {
            'id': self.type.id,
            'label': self.type.label,
            'pictogram': self.type.pictogram.url,
        }

    @property
    def latitude(self):
        if self.geom:
            api_geom = self.geom.transform(settings.API_SRID, clone=True)
            return api_geom.y
        return None

    @property
    def longitude(self):
        if self.geom:
            api_geom = self.geom.transform(settings.API_SRID, clone=True)
            return api_geom.x
        return None

    @property
    def thumbnail(self):
        if not self.photo:
            return None
        thumbnailer = get_thumbnailer(self.photo)
        try:
            return thumbnailer.get_thumbnail(aliases.get('thumbnail'))
        except (IOError, InvalidImageFormatError):
            logger.warning(
                _("Image %s invalid or missing from disk.") % self.photo)
            return None

    @property
    def resized_picture(self):
        if not self.photo:
            return None
        thumbnailer = get_thumbnailer(self.photo)
        try:
            return thumbnailer.get_thumbnail(aliases.get('medium'))
        except (IOError, InvalidImageFormatError):
            logger.warning(
                _("Image %s invalid or missing from disk.") % self.photo)
            return None

    @property
    def photo_url(self):
        thumbnail = self.thumbnail
        if not thumbnail:
            return None
        return os.path.join(settings.MEDIA_URL, thumbnail.name)
Exemplo n.º 15
0
class DownloadRequest(models.Model):
    email_address = models.EmailField()
    items = models.ManyToManyField(BoundedItem, blank=True)
    #stored as array of string JSON, only used once so that's okay in this case, change if upgraded to Postgres 9.4+
    #FIXME: remove, no longer used
    external_items = ArrayField(models.TextField(), null=True, blank=True)
    date = models.DateTimeField(auto_now_add=True)
    active = models.BooleanField(default=False)
    image_width = models.IntegerField(default=2000)
    wfs_format = models.TextField(null=True, blank=True)

    def create_zip_file_and_notify(self, **kwargs):
        i = self

        if not i.active:
            return

        zip_file_path = "/media/zip/place_data_for_%s_%d.zip" % (
            i.email_address, i.id)
        zip_file = zipfile.ZipFile(
            "%s/%s" % (settings.BASE_DIR, zip_file_path), "w")

        for item in i.items.all():
            location = "{%s}" if not item.Location[0] == '{' else item.Location
            location = json.loads(location)

            if item.DataType == "Book":
                pdf_file_path = "%s/media/pdf/%s.pdf" % (settings.BASE_DIR,
                                                         item.Name)
                zip_file.write(pdf_file_path,
                               "place_data/%s.pdf" % (item.LayerId))
                zip_file.writestr("place_data/%s.xml" % (item.LayerId),
                                  item.FgdcText.encode('utf-8'))
            elif "wfs" in location:
                wfs_path = location["wfs"][0]
                data_file_path = "%s/temp/%s_%d.json" % (settings.BASE_DIR,
                                                         item.LayerId, i.id)
                data_url = "http://%s/external_wfs/%s?request=GetFeature&typeName=%s:%s&outputFormat=%s&srsName=EPSG:4326" % (
                    settings.ALLOWED_HOSTS[0], wfs_path.replace("http://", ""),
                    item.WorkspaceName, item.Name, i.wfs_format)
                urlretrieve(data_url, data_file_path)

                extension = None
                if i.wfs_format == "shape-zip":
                    extension = "zip"
                elif i.wfs_format == "GML2" or i.wfs_format == "GML3":
                    extension = "gml"
                elif i.wfs_format == "KML":
                    extension = "kml"
                else:
                    extension = "json"

                zip_file.write(data_file_path,
                               "place_data/%s.%s" % (item.LayerId, extension))
                os.remove(data_file_path)
                zip_file.writestr("place_data/%s.xml" % (item.LayerId),
                                  item.FgdcText.encode('utf-8'))
            elif "wms" in location:
                wms_path = location["wms"][0]
                image_file_path = "%s/temp/%s_%d.tiff" % (settings.BASE_DIR,
                                                          item.Name, i.id)
                image_url = "%s/reflect?format=image/geotiff&transparent=true&width=%d&layers=%s" % (
                    wms_path, i.image_width, item.Name)
                urlretrieve(image_url, image_file_path)
                zip_file.write(image_file_path,
                               "place_data/%s.tif" % (item.LayerId))
                os.remove(image_file_path)
                zip_file.writestr("place_data/%s.xml" % (item.LayerId),
                                  item.FgdcText.encode('utf-8'))

        zip_file.close()

        #   send mail with zip link
        mail_server = smtplib.SMTP('cisunix.unh.edu')
        message = MIMEText(
            "Your download request is located at http://%s%s. It will be deleted after 24 hours."
            % (settings.ALLOWED_HOSTS[0], zip_file_path))
        message["Subject"] = "PLACE Data ready for download"
        message["To"] = i.email_address
        message["From"] = "*****@*****.**"
        mail_server.sendmail(message["From"], [i.email_address],
                             message.as_string())

        i.active = False
        i.save()
Exemplo n.º 16
0
class Person(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    mail = models.EmailField()
Exemplo n.º 17
0
class Profile(models.Model):
    email = models.EmailField()
Exemplo n.º 18
0
class Project(models.Model):

    owner = models.ForeignKey(User,
                              null=True,
                              blank=True,
                              related_name="owner")
    editors = models.ManyToManyField(User, related_name="editors")

    nom = models.CharField(u"Nom de l'ÉcoQuartier", max_length=255)  #
    mise_a_jour = models.DateField(auto_now_add=True)  #
    statut = models.ForeignKey(Statut, null=True)  #
    zonage_insee = models.ForeignKey(ZonageINSEE,
                                     null=True,
                                     verbose_name="Zonage INSEE")  #
    commune = models.ForeignKey(
        Commune,
        null=True,
        verbose_name="Commune principale",
        help_text=u"Sur quelle commune est situé l'ÉcoQuartier")  #
    communes = models.ManyToManyField(Commune,
                                      related_name="other_communes")  #
    population = models.IntegerField(default=0)  #
    description = models.TextField("Description du projet",
                                   help_text="10 lignes maximum")  #
    contexte_commune = models.ForeignKey(ContexteCommune, null=True)  #
    littorale = models.BooleanField(default=False)  #
    montagne = models.BooleanField(default=False)  #
    autres_communes = models.TextField()
    adresse = models.TextField()  #
    systeme_projection = models.CharField(max_length=255)  #
    coordonnees_geographiques = models.GeometryCollectionField(blank=True,
                                                               null=True)  #
    site = models.TextField(
        "Caractéristiques initiales du site",
        help_text=
        u"Préciser en 5 lignes maximum les caractéristiques initiales du site : par exemple, terrains agricoles, site militaire, friches industrielles, quartier d’habitat social..."
    )  #
    contexte_site = models.TextField("Contexte du site",
                                     help_text=context_site_help)  #
    type_operations = models.ManyToManyField(
        TypeOperation,
        verbose_name="Type d'opérations (plusieurs choix possibles)",
        help_text="Plusieurs choix possibles")  #
    type_operation_autre = models.TextField()  #
    vocations = models.ManyToManyField(Vocation)  #
    vocation_autre = models.TextField()  #
    superficieha = models.FloatField(
        u"Superficie de l'opération",
        help_text=u"Quelle est la superficie de l'EcoQuartier ? (ha)",
        null=True)  #
    surface_nonbatie = models.FloatField(
        u"Surface non bâtie publique",
        help_text=
        u"Toute surface non bâtie appartenant au domaine public, notamment voirie, espaces verts, espaces publics",
        null=True)  #
    habitants = models.IntegerField(u"Nombre d'habitants prévus", default=0)  #
    logements = models.IntegerField(u"Nombre de logements",
                                    help_text=logements_help,
                                    default=0)  #
    shon_logementsm = models.IntegerField(
        u"SHON logement",
        help_text=u"Surface hors œuvre net des logements",
        default=0)  #
    logements_sociau = models.IntegerField(u"Nombre de logements sociaux",
                                           help_text=u"",
                                           null=True,
                                           blank=True)  #
    logements_sociaux_detail = models.TextField(u"Logements sociaux détail")
    equipements_publics = models.TextField(
        u"Détail équipements publics",
        help_text=u"Précisions sur les équipements publics considérés")  #
    shon_equipementsm = models.IntegerField(u"Surface équipements publics",
                                            null=True,
                                            blank=True)  #
    commerces_services = models.TextField(
        u"Détail commerces et services",
        help_text=
        u"Préciser le type de commerces et services programmés dans l'opération"
    )  #
    shon_commercesm = models.IntegerField(
        u"Surface de plancher des commerces et services",
        null=True,
        blank=True)
    bureaux_activites = models.TextField(
        u"Détail bureaux et activités",
        help_text=u"Préciser le type d'activités prévues dans l'opération")
    shon_bureauxm = models.IntegerField(
        u"Surface de plancher bureaux et activités", null=True, blank=True)
    programme_detail = models.TextField()  #
    densite_brute = models.IntegerField(null=True, blank=True)  #
    densite_brute_logements = models.IntegerField(null=True, blank=True)  #
    densite_logements = models.IntegerField(null=True, blank=True)  #
    projet_social = models.TextField()  #
    economie_circulaire = models.TextField()  #

    charte = models.FileField(upload_to='charte/%Y/%m/%d/',
                              null=True,
                              blank=True,
                              verbose_name="Charte ÉcoQuartier")
    charte_date = models.DateField(null=True, blank=True)
    demarches = models.ManyToManyField(
        Demarche,
        verbose_name=
        u"Engagement dans d'autres démarches de développement durable")
    demarches_autres = models.CharField(u"Autres démarches",
                                        max_length=255,
                                        null=True,
                                        blank=True)
    echelle = models.ForeignKey(Echelle, null=True, blank=True)

    def is_economie_circulaire(self):
        return True if self.economie_circulaire != '' else False

    is_economie_circulaire.boolean = True
    is_economie_circulaire.short_description = u'économie circulaire'

    attenuation_changement_climatique = models.TextField()  #

    def is_attenuation_changement_climatique(self):
        return True if self.attenuation_changement_climatique != '' else False

    is_attenuation_changement_climatique.boolean = True
    is_attenuation_changement_climatique.short_description = u'atténuation du changement climatique'

    label_demarche = models.TextField()  #

    def is_label_demarche(self):
        return True if self.label_demarche != '' else False

    is_label_demarche.boolean = True
    is_label_demarche.short_description = u'label démarche'

    participation_2009 = models.BooleanField(default=False)  #
    participation_2011 = models.BooleanField(default=False)  #
    nomine = models.BooleanField(default=False)  #
    laureat = models.BooleanField(default=False)  #
    resultats_palmares = models.TextField()  #
    candidat_label = models.BooleanField(default=False)  #
    annee_candidature = models.IntegerField(null=True, blank=True)  #
    label_ecoquartier = models.ForeignKey(LabelEcoQuartier,
                                          null=True,
                                          verbose_name=u"État d'avancement")  #

    @property
    def state(self):
        if self.label_ecoquartier is None:
            return 'none'
        if self.label_ecoquartier.id == 3:
            return 'labeled'
        elif self.label_ecoquartier.id == 2:
            return 'engaged'
        else:
            return 'charte'

    annee_label = models.IntegerField(null=True, blank=True)  #
    procedure = models.ForeignKey(Procedure, null=True)  #
    procedure_detail = models.TextField()  #
    aspects_fonciers = models.TextField()  #
    etudes_prealables = models.TextField()  #
    concertation = models.TextField()  #
    collectivite_ou_epci_porteur = models.CharField(max_length=500)  #
    maitrise_ouvrage = models.TextField()  #
    maitrise_oeuvre = models.TextField()  #
    partenariats = models.TextField()  #
    opacrations_marquantes = models.TextField(
        u"Opérations marquantes du projet",
        help_text=
        u"<i>Si  au  sein  de  l’opération  d’aménagement,  une  ou  plusieurs  constructions  ou  espaces  publics  majeurs  méritent  d’être  remarquées,  vous  pouvez  nous  indiquer  ici  tous  les  éléments  nécessaires  (nom  du  bâtiment  ou  de  l’espace  public,  fonction,  maîtrise  d’ouvrage  et  maîtrise  d’œuvre,  particularités  de  l’opération, autres éléments ou photographies en votre possession...)</i>"
    )  #
    engagement = models.IntegerField(
        "Date d'engagement de l'opération",
        help_text=
        u"L’année d’engagement de l’opération : année de la première délibération concernant l’opération",
        null=True,
        blank=True)  #
    creation = models.IntegerField(
        u"Date de création de la ZAC",
        help_text=u"L’année de création de la ZAC (si ZAC)",
        null=True,
        blank=True)  #
    realisation = models.IntegerField(
        u"Date de réalisation de la ZAC",
        help_text="Année de réalisation de la ZAC (si ZAC)",
        null=True,
        blank=True)  #
    autorisation = models.IntegerField(
        u"Date d'autorisation d'aménager",
        help_text=u"Année d’autorisation d’aménager (si permis d’aménager)",
        null=True,
        blank=True)  #
    permis = models.IntegerField(
        u"Date du permis de construire",
        help_text=
        u"année du permis de construire (si opération se limite à un permis ou un permis groupé)",
        null=True,
        blank=True)  #
    debut = models.IntegerField(u"Date du début des travaux",
                                help_text=u"année du début des travaux",
                                null=True,
                                blank=True)  #
    livraison = models.IntegerField(
        u"Date de livraison des premiers bâtiments",
        help_text=u"Année de livraison des premiers bâtiments",
        null=True,
        blank=True)  #
    achevement = models.IntegerField(
        u"Date d'achèvement de l'opération",
        help_text=u"Année d’achèvement de l’opération",
        null=True,
        blank=True)  #
    complementaire = models.IntegerField(
        u"Date complémentaire",
        help_text=u"Autre date importante non citée ci-dessus",
        null=True,
        blank=True)  #
    coats = models.TextField()  #
    sources = models.TextField()  #
    sources_details = models.TextField()  #
    contact = models.TextField()  #
    project_manager_lastname = models.CharField("Nom",
                                                max_length=255,
                                                null=True,
                                                blank=True)
    project_manager_firstname = models.CharField("Prénom",
                                                 max_length=255,
                                                 null=True,
                                                 blank=True)
    project_manager_mail = models.EmailField("Mail",
                                             max_length=255,
                                             null=True,
                                             blank=True)
    project_manager_structure = models.CharField("Organisme de rattachement",
                                                 max_length=255,
                                                 null=True,
                                                 blank=True)

    project_developer_lastname = models.CharField("Nom",
                                                  max_length=255,
                                                  null=True,
                                                  blank=True)
    project_developer_firstname = models.CharField("Prénom",
                                                   max_length=255,
                                                   null=True,
                                                   blank=True)
    project_developer_mail = models.EmailField("Mail",
                                               max_length=255,
                                               null=True,
                                               blank=True)
    project_developer_structure = models.CharField("Organisme de rattachement",
                                                   max_length=255,
                                                   null=True,
                                                   blank=True)

    plusieurs_tranches = models.BooleanField(
        u"L'opération comporte plusieurs tranches", default=False)

    sites_enlien = models.TextField()  #
    documents = models.TextField()  #

    eau = models.TextField()  #
    dechets = models.TextField()  #
    biodiversite = models.TextField()  #
    mobilite = models.TextField()  #
    sobriete_energetique_et_energie_renouvelable = models.TextField()  #
    densite_et_formes_urbaines = models.TextField()  #
    ecoconstruction = models.TextField()  #
    autres = models.TextField()  #
    demarches_et_processus = models.TextField()  #
    cadre_de_vie_et_usages = models.TextField()  #

    def is_eau(self):
        return True if self.eau != '' else False

    is_eau.boolean = True
    is_eau.short_description = 'eau'

    def is_dechets(self):
        return True if self.dechets != '' else False

    is_dechets.boolean = True
    is_dechets.short_description = 'dechets'

    def is_biodiversite(self):
        return True if self.biodiversite != '' else False

    is_biodiversite.boolean = True
    is_biodiversite.short_description = 'biodiversite'

    def is_mobilite(self):
        return True if self.mobilite != '' else False

    is_mobilite.boolean = True
    is_mobilite.short_description = 'mobilite'

    def is_sobriete_energetique_et_energie_renouvelable(self):
        return True if self.sobriete_energetique_et_energie_renouvelable != '' else False

    is_sobriete_energetique_et_energie_renouvelable.boolean = True
    is_sobriete_energetique_et_energie_renouvelable.short_description = 'sobriete energetique et energie renouvelable'

    def is_densite_et_formes_urbaines(self):
        return True if self.densite_et_formes_urbaines != '' else False

    is_densite_et_formes_urbaines.boolean = True
    is_densite_et_formes_urbaines.short_description = 'densite et formes urbaines'

    def is_ecoconstruction(self):
        return True if self.ecoconstruction != '' else False

    is_ecoconstruction.boolean = True
    is_ecoconstruction.short_description = 'ecoconstruction'

    def is_demarches_et_processus(self):
        return True if self.demarches_et_processus != '' else False

    is_demarches_et_processus.boolean = True
    is_demarches_et_processus.short_description = 'demarches et processus'

    def is_cadre_de_vie_et_usages(self):
        return True if self.cadre_de_vie_et_usages != '' else False

    is_cadre_de_vie_et_usages.boolean = True
    is_cadre_de_vie_et_usages.short_description = 'cadre de vie et usages'

    tags = models.ManyToManyField(Tag, verbose_name="Points forts du projet")

    commentaires_demarche_et_processus = models.TextField()
    ambition_1 = models.TextField()
    ambition_2 = models.TextField()
    ambition_3 = models.TextField()
    ambition_4 = models.TextField()
    ambition_5 = models.TextField()
    commentaires_cadre_de_vie_et_usages = models.TextField()
    ambition_6 = models.TextField()
    ambition_7 = models.TextField()
    ambition_8 = models.TextField()
    ambition_9 = models.TextField()
    ambition_10 = models.TextField()
    commentaires_developpement_territorial = models.TextField()
    ambition_11 = models.TextField()
    ambition_12 = models.TextField()
    ambition_13 = models.TextField()
    ambition_14 = models.TextField()
    ambition_15 = models.TextField()
    commentaires_environnement_et_climat = models.TextField()
    ambition_16 = models.TextField()
    ambition_17 = models.TextField()
    ambition_18 = models.TextField()
    ambition_19 = models.TextField()
    ambition_20 = models.TextField()
    synthese_demarche_et_processus = models.TextField()
    engagement_1 = models.TextField(
        "Engagement 1 (2000 signes maximum sans compter les espaces)",
        help_text=engagement_1_help)
    engagement_2 = models.TextField()
    engagement_3 = models.TextField()
    engagement_4 = models.TextField()
    engagement_5 = models.TextField()
    synthese_cadre_de_vie_et_usages = models.TextField()
    engagement_6 = models.TextField()
    engagement_7 = models.TextField()
    engagement_8 = models.TextField()
    engagement_9 = models.TextField(help_text=engagement_9_help)
    engagement_10 = models.TextField()
    synthese_developpement_territorial = models.TextField()
    engagement_11 = models.TextField()
    engagement_12 = models.TextField()
    engagement_13 = models.TextField()
    engagement_14 = models.TextField()
    engagement_15 = models.TextField()
    synthese_environnement_et_climat = models.TextField()
    engagement_16 = models.TextField()
    engagement_17 = models.TextField()
    engagement_18 = models.TextField()
    engagement_19 = models.TextField()
    engagement_20 = models.TextField()

    indicateur_i1 = models.TextField()
    valeur_i1 = models.TextField()
    indicateur_i2 = models.TextField()
    valeur_i2 = models.TextField()
    indicateur_i3 = models.TextField()
    valeur_i3 = models.TextField()
    indicateur_i4 = models.TextField()
    valeur_i4 = models.TextField()
    indicateur_i5 = models.TextField()
    valeur_i5 = models.TextField()
    indicateur_i6 = models.TextField()
    valeur_i6 = models.TextField()
    indicateur_i7 = models.TextField()
    valeur_i7 = models.TextField()
    indicateur_i8 = models.TextField()
    valeur_i8 = models.TextField()
    indicateur_i9 = models.TextField()
    valeur_i9 = models.TextField()
    indicateur_i10 = models.TextField()
    valeur_i10 = models.TextField()
    indicateur_i11 = models.TextField()
    valeur_i11 = models.TextField()
    indicateur_i12 = models.TextField()
    valeur_i12 = models.TextField()
    indicateur_i13 = models.TextField()
    valeur_i13 = models.TextField()
    indicateur_i14 = models.TextField()
    valeur_i14 = models.TextField()
    indicateur_i15 = models.TextField()
    valeur_i15 = models.TextField()
    indicateur_i16 = models.TextField()
    valeur_i16 = models.TextField()
    indicateur_i17 = models.TextField()
    valeur_i17 = models.TextField()
    indicateur_i18 = models.TextField()
    valeur_i18 = models.TextField()
    indicateur_i19 = models.TextField()
    valeur_i19 = models.TextField()
    indicateur_i20 = models.TextField()
    valeur_i20 = models.TextField()
    indicateur_i21 = models.TextField()
    valeur_i21 = models.TextField()
    indicateur_i22 = models.TextField()
    valeur_i22 = models.TextField()
    indicateur_i23 = models.TextField()
    valeur_i23 = models.TextField()
    indicateur_i24 = models.TextField()
    valeur_i24 = models.TextField()
    indicateur_i25 = models.TextField()
    valeur_i25 = models.TextField()
    indicateur_i26 = models.TextField()
    valeur_i26 = models.TextField()
    indicateur_i27 = models.TextField()
    valeur_i27 = models.TextField()
    indicateur_i28 = models.TextField()
    valeur_i28 = models.TextField()
    indicateur_i29 = models.TextField()
    valeur_i29 = models.TextField()
    indicateur_i30 = models.TextField()
    valeur_i30 = models.TextField()
    indicateur_i31 = models.TextField()
    valeur_i31 = models.TextField()
    indicateur_i32 = models.TextField()
    valeur_i32 = models.TextField()
    indicateur_i33 = models.TextField()
    valeur_i33 = models.TextField()
    indicateur_i34 = models.TextField()
    valeur_i34 = models.TextField()
    indicateur_i35 = models.TextField()
    valeur_i35 = models.TextField()
    indicateur_i36 = models.TextField()
    valeur_i36 = models.TextField()
    indicateur_i37 = models.TextField()
    valeur_i37 = models.TextField()
    indicateur_i38 = models.TextField()
    valeur_i38 = models.TextField()
    indicateur_i39 = models.TextField()
    valeur_i39 = models.TextField()
    indicateur_i40 = models.TextField()
    valeur_i40 = models.TextField()
    indicateur_i41 = models.TextField()
    valeur_i41 = models.TextField()
    indicateur_i42 = models.TextField()
    valeur_i42 = models.TextField()
    indicateur_i43 = models.TextField()
    valeur_i43 = models.TextField()
    indicateur_i44 = models.TextField()
    valeur_i44 = models.TextField()
    indicateur_i45 = models.TextField()
    valeur_i45 = models.TextField()
    indicateur_i46 = models.TextField()
    valeur_i46 = models.TextField()
    indicateur_i47 = models.TextField()
    valeur_i47 = models.TextField()
    indicateur_i48 = models.TextField()
    valeur_i48 = models.TextField()
    indicateur_i49 = models.TextField()
    valeur_i49 = models.TextField()
    indicateur_i50 = models.TextField()
    valeur_i50 = models.TextField()

    commentaires = models.TextField()

    objects = models.GeoManager()

    plan_situation_1_5000 = models.FileField(upload_to="upload",
                                             null=True,
                                             blank=True)
    plan_masse_1_1000 = models.FileField(upload_to="upload",
                                         null=True,
                                         blank=True)
    plan_masse_1_500 = models.FileField(upload_to="upload",
                                        null=True,
                                        blank=True)
    plan_detaille = models.FileField(upload_to="upload", null=True, blank=True)

    MAITRISE_OUVRAGE_STRUCTURE_CHOICES = (
        ('RC', u'régie communale'),
        ('SM', u'SEM'),
        ('SP', u'SPLA'),
        ('AP', u'Aménageur privé'),
    )

    maitrise_ouvrage_structure = models.CharField(
        max_length=2,
        choices=MAITRISE_OUVRAGE_STRUCTURE_CHOICES,
        null=True,
        blank=True)
    maitrise_ouvrage_nom = models.CharField(max_length=255,
                                            null=True,
                                            blank=True)

    partenaires = models.ManyToManyField(Partenaire,
                                         through='PartenaireDetail')

    @property
    def commune_label(self):
        return self.commune.label

    @property
    def short_description(self):
        return Truncator(self.description).words(50,
                                                 html=False,
                                                 truncate=' ...')

    @property
    def feature(self):
        return self.photos[0] if len(self.photos) > 0 else None

    @property
    def photos(self):
        return [
            photo.photo.url
            for photo in self.projectphoto_set.exclude(photo__isnull=True)
        ]

    @property
    def engagement_1_completed(self):
        fields = [
            'site',
            'contexte_site',
            'superficieha',
            'surface_nonbatie',
            'engagement',
            'creation',
            'realisation',
            'autorisation',
            'permis',
            'debut',
            'livraison',
            'achevement',
            'complementaire',
            'programme_detail',
            'etudes_prealables',
            'opacrations_marquantes',
            'habitants',
            'logements',
            'shon_logementsm',
            'logements_sociau',
            'equipements_publics',
            'shon_equipementsm',
            'commerces_services',
            'shon_commercesm',
            'bureaux_activites',
            'shon_bureauxm',
            'engagement_1',
        ]
        return self.completed(fields)

    @property
    def engagement_2_completed(self):
        fields = [
            'procedure', 'procedure_detail', 'concertation',
            'collectivite_ou_epci_porteur', 'maitrise_ouvrage',
            'maitrise_oeuvre', 'partenariats', 'engagement_2'
        ]
        return self.completed(fields)

    @property
    def engagement_3_completed(self):
        fields = ['coats', 'engagement_3']
        return self.completed(fields)

    @property
    def engagement_4_completed(self):
        fields = ['engagement_4']
        return self.completed(fields)

    @property
    def engagement_5_completed(self):
        fields = ['engagement_5']
        return self.completed(fields)

    @property
    def dimension_1_completed(self):
        values = [
            self.engagement_1_completed, self.engagement_2_completed,
            self.engagement_3_completed, self.engagement_4_completed,
            self.engagement_5_completed
        ]
        return sum(values)

    @property
    def engagement_6_completed(self):
        fields = [
            'aspects_fonciers', 'densite_brute', 'densite_brute_logements',
            'densite_logements', 'surface_nonbatie', 'engagement_6'
        ]
        return self.completed(fields)

    @property
    def engagement_7_completed(self):
        fields = [
            'habitants', 'logements', 'shon_logementsm', 'logements_sociau',
            'engagement_7'
        ]
        return self.completed(fields)

    @property
    def engagement_8_completed(self):
        fields = ['coats', 'engagement_8']
        return self.completed(fields)

    @property
    def engagement_9_completed(self):
        fields = ['opacrations_marquantes', 'engagement_9']
        return self.completed(fields)

    @property
    def engagement_10_completed(self):
        fields = ['contexte_site', 'engagement_10']
        return self.completed(fields)

    @property
    def dimension_2_completed(self):
        values = [
            self.engagement_6_completed,
            self.engagement_7_completed,
            self.engagement_8_completed,
            self.engagement_9_completed,
            self.engagement_10_completed,
        ]
        return sum(values)

    @property
    def engagement_11_completed(self):
        fields = [
            'equipements_publics', 'shon_equipementsm', 'commerces_services',
            'shon_commercesm', 'bureaux_activites', 'shon_bureauxm',
            'engagement_11'
        ]
        return self.completed(fields)

    @property
    def engagement_12_completed(self):
        fields = [
            'equipements_publics', 'shon_equipementsm', 'commerces_services',
            'shon_commercesm', 'bureaux_activites', 'shon_bureauxm',
            'engagement_12'
        ]
        return self.completed(fields)

    @property
    def engagement_13_completed(self):
        fields = ['engagement_13']
        return self.completed(fields)

    @property
    def engagement_14_completed(self):
        fields = ['engagement_14']
        return self.completed(fields)

    @property
    def engagement_15_completed(self):
        fields = ['engagement_15']
        return self.completed(fields)

    @property
    def dimension_3_completed(self):
        values = [
            self.engagement_11_completed,
            self.engagement_12_completed,
            self.engagement_13_completed,
            self.engagement_14_completed,
            self.engagement_15_completed,
        ]
        return sum(values)

    @property
    def engagement_16_completed(self):
        fields = ['engagement_16']
        return self.completed(fields)

    @property
    def engagement_17_completed(self):
        fields = ['engagement_17']
        return self.completed(fields)

    @property
    def engagement_18_completed(self):
        fields = ['engagement_18']
        return self.completed(fields)

    @property
    def engagement_19_completed(self):
        fields = ['engagement_19']
        return self.completed(fields)

    @property
    def engagement_20_completed(self):
        fields = ['engagement_20']
        return self.completed(fields)

    @property
    def dimension_4_completed(self):
        values = [
            self.engagement_16_completed, self.engagement_17_completed,
            self.engagement_18_completed, self.engagement_19_completed,
            self.engagement_20_completed
        ]
        return sum(values)

    @property
    def dimensions_completed(self):
        values = [
            self.dimension_1_completed, self.dimension_2_completed,
            self.dimension_3_completed, self.dimension_4_completed
        ]
        return all(values)

    def completed(self, fields):
        values = [
            getattr(self, field) != '' and getattr(self, field) is not None
            for field in fields
        ]
        if all(values):
            return True
        return False

    @models.permalink
    def get_absolute_url(self):
        return ('detail', (), {'pk': self.pk})

    @property
    def url(self):
        return reverse('detail', kwargs={'pk': self.id})

    def __unicode__(self):
        if self.commune.departement:
            return "%s (%s, %s)" % (self.nom, self.commune,
                                    self.commune.departement.region)
        else:
            return "%s (%s)" % (self.nom, self.commune)

    def save(self, *args, **kwargs):
        # update charte_date when a charte is added
        if self.pk is not None:
            orig = Project.objects.get(pk=self.pk)
            if orig.charte != self.charte:
                self.charte_date = date.today()
        else:
            if self.charte:
                self.charte_date = date.today()
        # update label_ecoquartier ("état d'avancement")
        if self.charte_date:
            self.label_ecoquartier = LabelEcoQuartier.objects.get(id=5)
            if self.annee_candidature:
                self.label_ecoquartier = LabelEcoQuartier.objects.get(id=2)
                if self.annee_label:
                    self.label_ecoquartier = LabelEcoQuartier.objects.get(id=3)
        super(Project, self).save(*args, **kwargs)
Exemplo n.º 19
0
class DepartmentUser(MPTTModel):
    """Represents a Department user. Maps to an object managed by Active Directory.
    """
    ACTIVE_FILTER = {
        "active": True,
        "email__isnull": False,
        "cost_centre__isnull": False,
        "contractor": False
    }
    # The following choices are intended to match options in Alesco.
    ACCOUNT_TYPE_CHOICES = (
        (3, 'Agency contract'),
        (0, 'Department fixed-term contract'),
        (1, 'Other'),
        (2, 'Permanent'),
        (4, 'Resigned'),
        (9, 'Role-based account'),
        (8, 'Seasonal'),
        (5, 'Shared account'),
        (6, 'Vendor'),
        (7, 'Volunteer'),
    )
    POSITION_TYPE_CHOICES = (
        (0, 'Full time'),
        (1, 'Part time'),
        (2, 'Casual'),
        (3, 'Other'),
    )
    # These fields are populated from Active Directory.
    date_created = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)
    cost_centre = models.ForeignKey("organisation.CostCentre",
                                    on_delete=models.PROTECT,
                                    null=True)
    cost_centres_secondary = models.ManyToManyField(
        "organisation.CostCentre",
        related_name="cost_centres_secondary",
        blank=True,
        help_text='NOTE: this provides security group access (e.g. T drives).')
    org_unit = models.ForeignKey(
        "organisation.OrgUnit",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        verbose_name='organisational unit',
        help_text="""The organisational unit that represents the user's"""
        """ primary physical location (also set their distribution group).""")
    org_units_secondary = models.ManyToManyField(
        "organisation.OrgUnit",
        related_name="org_units_secondary",
        blank=True,
        help_text='NOTE: this provides email distribution group access.')
    extra_data = JSONField(null=True, blank=True)
    ad_guid = models.CharField(max_length=48, unique=True, editable=False)
    ad_dn = models.CharField(max_length=512, unique=True, editable=False)
    ad_data = JSONField(null=True, blank=True, editable=False)
    org_data = JSONField(null=True, blank=True, editable=False)
    employee_id = models.CharField(
        max_length=128,
        null=True,
        unique=True,
        blank=True,
        verbose_name='Employee ID',
        help_text="HR Employee ID, use 'n/a' if a contractor")
    email = models.EmailField(unique=True, editable=False)
    username = models.CharField(max_length=128,
                                editable=False,
                                unique=True,
                                help_text='Pre-Windows 2000 login username.')
    name = models.CharField(max_length=128,
                            help_text='Format: Surname, Given name')
    given_name = models.CharField(
        max_length=128,
        null=True,
        help_text='Legal first name (matches birth certificate/password/etc.)')
    surname = models.CharField(
        max_length=128,
        null=True,
        help_text='Legal surname (matches birth certificate/password/etc.)')
    name_update_reference = models.CharField(
        max_length=512,
        null=True,
        blank=True,
        verbose_name='update reference',
        help_text='Reference for name/CC change request')
    preferred_name = models.CharField(
        max_length=256,
        null=True,
        blank=True,
        help_text='Employee-editable preferred name.')
    title = models.CharField(
        max_length=128,
        null=True,
        help_text='Occupation position title (should match Alesco)')
    position_type = models.PositiveSmallIntegerField(
        choices=POSITION_TYPE_CHOICES,
        null=True,
        blank=True,
        default=0,
        help_text=
        'Employee position working arrangement (should match Alesco status)')
    parent = TreeForeignKey('self',
                            on_delete=models.PROTECT,
                            null=True,
                            blank=True,
                            related_name='children',
                            editable=True,
                            verbose_name='Reports to',
                            help_text='Person that this employee reports to')
    expiry_date = models.DateTimeField(
        null=True,
        editable=False,
        help_text='Date that the AD account is set to expire.')
    date_ad_updated = models.DateTimeField(
        null=True,
        editable=False,
        verbose_name='Date AD updated',
        help_text='The date when the AD account was last updated.')
    telephone = models.CharField(max_length=128, null=True, blank=True)
    mobile_phone = models.CharField(max_length=128, null=True, blank=True)
    extension = models.CharField(max_length=128,
                                 null=True,
                                 blank=True,
                                 verbose_name='VoIP extension')
    home_phone = models.CharField(max_length=128, null=True, blank=True)
    other_phone = models.CharField(max_length=128, null=True, blank=True)
    active = models.BooleanField(
        default=True,
        editable=False,
        help_text='Account is active within Active Directory.')
    ad_deleted = models.BooleanField(
        default=False,
        editable=False,
        help_text='Account has been deleted in Active Directory.')
    in_sync = models.BooleanField(
        default=False,
        editable=False,
        help_text='CMS data has been synchronised from AD data.')
    vip = models.BooleanField(
        default=False,
        help_text=
        "An individual who carries out a critical role for the department")
    executive = models.BooleanField(
        default=False, help_text="An individual who is an executive")
    contractor = models.BooleanField(
        default=False,
        help_text=
        "An individual who is an external contractor (does not include agency contract staff)"
    )
    photo = models.ImageField(blank=True, upload_to=get_photo_path)
    photo_ad = models.ImageField(blank=True,
                                 editable=False,
                                 upload_to=get_photo_ad_path)
    sso_roles = models.TextField(
        null=True,
        editable=False,
        help_text="Groups/roles separated by semicolon")
    notes = models.TextField(null=True,
                             blank=True,
                             help_text="Officer secondary roles, etc.")
    working_hours = models.TextField(
        default="N/A",
        null=True,
        blank=True,
        help_text="Description of normal working hours")
    secondary_locations = models.ManyToManyField("organisation.Location",
                                                 blank=True)
    populate_primary_group = models.BooleanField(
        default=True,
        help_text="If unchecked, user will not be added to primary group email"
    )
    account_type = models.PositiveSmallIntegerField(
        choices=ACCOUNT_TYPE_CHOICES,
        null=True,
        blank=True,
        help_text='Employee account status (should match Alesco status)')
    alesco_data = JSONField(null=True,
                            blank=True,
                            help_text='Readonly data from Alesco')
    security_clearance = models.BooleanField(
        default=False,
        verbose_name='security clearance granted',
        help_text='''Security clearance approved by CC Manager (confidentiality
        agreement, referee check, police clearance, etc.''')
    o365_licence = models.NullBooleanField(
        default=None,
        editable=False,
        help_text='Account consumes an Office 365 licence.')
    shared_account = models.BooleanField(
        default=False,
        editable=False,
        help_text='Automatically set from account type.')

    class MPTTMeta:
        order_insertion_by = ['name']

    class Meta:
        ordering = ('name', )

    def __init__(self, *args, **kwargs):
        super(DepartmentUser, self).__init__(*args, **kwargs)
        # Store the pre-save values of some fields on object init.
        self.__original_given_name = self.given_name
        self.__original_surname = self.surname
        self.__original_employee_id = self.employee_id
        self.__original_cost_centre = self.cost_centre
        self.__original_name = self.name
        self.__original_org_unit = self.org_unit

    def __str__(self):
        return self.email

    def save(self, *args, **kwargs):
        """Override the save method with additional business logic.
        """
        if self.employee_id and self.employee_id.lower() == "n/a":
            self.employee_id = None
        if self.employee_id:
            self.employee_id = "{0:06d}".format(int(self.employee_id))
        self.in_sync = True if self.date_ad_updated else False
        # If the CC is set but not the OrgUnit, use the CC's OrgUnit.
        if self.cost_centre and not self.org_unit:
            self.org_unit = self.cost_centre.org_position
        if self.cost_centre and self.org_unit:
            self.org_data = self.org_data or {}
            self.org_data["units"] = list(
                self.org_unit.get_ancestors(include_self=True).values(
                    "id", "name", "acronym", "unit_type", "costcentre__code",
                    "costcentre__name", "location__name"))
            self.org_data["unit"] = self.org_data["units"][-1]
            if self.org_unit.location:
                self.org_data["location"] = self.org_unit.location.as_dict()
            if self.org_unit.secondary_location:
                self.org_data[
                    "secondary_location"] = self.org_unit.secondary_location.as_dict(
                    )
            for unit in self.org_data["units"]:
                unit["unit_type"] = self.org_unit.TYPE_CHOICES_DICT[
                    unit["unit_type"]]
            self.org_data["cost_centre"] = {
                "name": self.org_unit.name,
                "code": self.cost_centre.code,
                "cost_centre_manager": str(self.cost_centre.manager),
                "business_manager": str(self.cost_centre.business_manager),
                "admin": str(self.cost_centre.admin),
                "tech_contact": str(self.cost_centre.tech_contact),
            }
            if self.cost_centres_secondary.exists():
                self.org_data['cost_centres_secondary'] = [{
                    'name': i.name,
                    'code': i.code,
                } for i in self.cost_centres_secondary.all()]
            if self.org_units_secondary:
                self.org_data['org_units_secondary'] = [{
                    'name':
                    i.name,
                    'acronym':
                    i.name,
                    'unit_type':
                    i.get_unit_type_display(),
                } for i in self.org_units_secondary.all()]
        try:
            self.update_photo_ad()
        except:  # Don't bomb out of saving for update_photo_ad errors.
            pass
        if self.account_type in [5, 9]:  # Shared/role-based account types.
            self.shared_account = True
        super(DepartmentUser, self).save(*args, **kwargs)

    def update_photo_ad(self):
        # Update self.photo_ad to a 240x240 thumbnail >10 kb in size.
        if not self.photo:
            if self.photo_ad:
                self.photo_ad.delete()
            return

        from PIL import Image
        from six import BytesIO
        from django.core.files.base import ContentFile

        if hasattr(self.photo.file, 'content_type'):
            PHOTO_TYPE = self.photo.file.content_type

            if PHOTO_TYPE == 'image/jpeg':
                PIL_TYPE = 'jpeg'
            elif PHOTO_TYPE == 'image/png':
                PIL_TYPE = 'png'
            else:
                return
        else:
            PIL_TYPE = 'jpeg'
        # good defaults to get ~10kb JPEG images
        PHOTO_AD_SIZE = (240, 240)
        PIL_QUALITY = 75
        # remote file size limit
        PHOTO_AD_FILESIZE = 10000

        image = Image.open(BytesIO(self.photo.read()))
        image.thumbnail(PHOTO_AD_SIZE, Image.LANCZOS)

        # in case we miss 10kb, drop the quality and recompress
        for i in range(12):
            temp_buffer = BytesIO()
            image.save(temp_buffer,
                       PIL_TYPE,
                       quality=PIL_QUALITY,
                       optimize=True)
            length = temp_buffer.tell()
            if length <= PHOTO_AD_FILESIZE:
                break
            if PIL_TYPE == 'png':
                PIL_TYPE = 'jpeg'
            else:
                PIL_QUALITY -= 5

        temp_buffer.seek(0)
        self.photo_ad.save(os.path.basename(self.photo.name),
                           ContentFile(temp_buffer.read()),
                           save=False)

    def org_data_pretty(self):
        if not self.org_data:
            return self.org_data
        return format_html(json2html.convert(json=self.org_data))

    def ad_data_pretty(self):
        if not self.ad_data:
            return self.ad_data
        return format_html(json2html.convert(json=self.ad_data))

    def alesco_data_pretty(self):
        if not self.alesco_data:
            return self.alesco_data
        # Manually generate HTML table output, to guarantee field order.
        t = '''<table border="1">
            <tr><th>FIRST_NAME</th><td>{FIRST_NAME}</td></tr>
            <tr><th>SECOND_NAME</th><td>{SECOND_NAME}</td></tr>
            <tr><th>SURNAME</th><td>{SURNAME}</td></tr>
            <tr><th>EMPLOYEE_NO</th><td>{EMPLOYEE_NO}</td></tr>
            <tr><th>PAYPOINT</th><td>{PAYPOINT}</td></tr>
            <tr><th>PAYPOINT_DESC</th><td>{PAYPOINT_DESC}</td></tr>
            <tr><th>MANAGER_POS#</th><td>{MANAGER_POS#}</td></tr>
            <tr><th>MANAGER_NAME</th><td>{MANAGER_NAME}</td></tr>
            <tr><th>JOB_NO</th><td>{JOB_NO}</td></tr>
            <tr><th>FIRST_COMMENCE</th><td>{FIRST_COMMENCE}</td></tr>
            <tr><th>OCCUP_TERM_DATE</th><td>{OCCUP_TERM_DATE}</td></tr>
            <tr><th>POSITION_NO</th><td>{POSITION_NO}</td></tr>
            <tr><th>OCCUP_POS_TITLE</th><td>{OCCUP_POS_TITLE}</td></tr>
            <tr><th>LOC_DESC</th><td>{LOC_DESC}</td></tr>
            <tr><th>CLEVEL1_ID</th><td>{CLEVEL1_ID}</td></tr>
            <tr><th>CLEVEL2_DESC</th><td>{CLEVEL2_DESC}</td></tr>
            <tr><th>CLEVEL3_DESC</th><td>{CLEVEL3_DESC}</td></tr>
            <tr><th>EMP_STAT_DESC</th><td>{EMP_STAT_DESC}</td></tr>
            <tr><th>GEO_LOCATION_DESC</th><td>{GEO_LOCATION_DESC}</td></tr>
            </table>'''
        t = t.format(**self.alesco_data)
        return mark_safe(t)

    @property
    def password_age_days(self):
        if self.ad_data and 'pwdLastSet' in self.ad_data:
            try:
                td = datetime.now() - convert_ad_timestamp(
                    self.ad_data['pwdLastSet'])
                return td.days
            except:
                pass
        return None
Exemplo n.º 20
0
class Report(MapEntityMixin, PicturesMixin, TimeStampedModelMixin):
    """ User reports, mainly submitted via *Geotrek-rando*.
    """
    email = models.EmailField(verbose_name=_("Email"))
    comment = models.TextField(blank=True,
                               default="",
                               verbose_name=_("Comment"))
    activity = models.ForeignKey('ReportActivity',
                                 on_delete=models.CASCADE,
                                 null=True,
                                 blank=True,
                                 verbose_name=_("Activity"))
    category = models.ForeignKey('ReportCategory',
                                 on_delete=models.CASCADE,
                                 null=True,
                                 blank=True,
                                 verbose_name=_("Category"))
    problem_magnitude = models.ForeignKey('ReportProblemMagnitude',
                                          null=True,
                                          blank=True,
                                          on_delete=models.CASCADE,
                                          verbose_name=_("Problem magnitude"))
    status = models.ForeignKey('ReportStatus',
                               on_delete=models.CASCADE,
                               null=True,
                               blank=True,
                               default=status_default,
                               verbose_name=_("Status"))
    geom = models.PointField(null=True,
                             blank=True,
                             default=None,
                             verbose_name=_("Location"),
                             srid=settings.SRID)
    related_trek = models.ForeignKey(Trek,
                                     null=True,
                                     blank=True,
                                     on_delete=models.CASCADE,
                                     verbose_name=_('Related trek'))

    class Meta:
        verbose_name = _("Report")
        verbose_name_plural = _("Reports")
        ordering = ['-date_insert']

    def __str__(self):
        if self.email:
            return self.email
        return "Anonymous report"

    @property
    def email_display(self):
        return '<a data-pk="%s" href="%s" title="%s" >%s</a>' % (self.pk,
                                                                 self.get_detail_url(),
                                                                 self,
                                                                 self)

    @property
    def full_url(self):
        try:
            return '{}{}'.format(
                settings.ALLOWED_HOSTS[0],
                self.get_detail_url()
            )
        except KeyError:
            # Do not display url if there is no ALLOWED_HOSTS
            return ""

    @classmethod
    def get_create_label(cls):
        return _("Add a new feedback")

    @property
    def geom_wgs84(self):
        return self.geom.transform(4326, clone=True)

    @property
    def comment_text(self):
        return html.unescape(self.comment)
Exemplo n.º 21
0
class AreaSoltura(models.Model):

    processo = models.IntegerField(null=True, blank=True)
    nome = models.CharField('Nome da propriedade',
                            max_length=255,
                            null=True,
                            blank=True)
    endereco = models.CharField('Endereço',
                                max_length=400,
                                null=True,
                                blank=True)
    uf = models.CharField('Unidade da Federação',
                          max_length=2,
                          null=True,
                          blank=True)
    municipio = models.CharField('Município',
                                 max_length=255,
                                 null=True,
                                 blank=True)
    proprietario = models.CharField('Nome do proprietário',
                                    max_length=255,
                                    null=True,
                                    blank=True)
    cpf = models.CharField('CPF', null=True, blank=True, max_length=11)
    telefone = models.CharField(max_length=15, null=True, blank=True)
    email = models.EmailField(null=True, blank=True)
    area = models.FloatField('Área da Propriedade (ha)', null=True, blank=True)
    arl_app = models.FloatField('Área de reserva legal e proteção permanente',
                                null=True,
                                blank=True)
    bioma = models.CharField('Bioma', max_length=255, null=True, blank=True)
    fitofisionomia = models.CharField(max_length=255, null=True, blank=True)
    taxon = models.CharField(max_length=255, null=True, blank=True)
    conservacao = models.NullBooleanField()
    conectividade = models.NullBooleanField()
    uc = models.NullBooleanField()
    agua = models.NullBooleanField()
    atividade = models.CharField('Atividade Econômica',
                                 max_length=255,
                                 null=True,
                                 blank=True)
    documento = models.NullBooleanField()
    mapa = models.NullBooleanField()
    carta = models.NullBooleanField()
    reabilitador = models.NullBooleanField()
    viveiros = models.PositiveSmallIntegerField('Número de viveiros',
                                                null=True,
                                                blank=True)
    distancia = models.FloatField('Distância até o CETAS mais próximo',
                                  null=True,
                                  blank=True)
    tempo = models.CharField('Tempo de viagem ao CETAS mais próximo',
                             max_length=5,
                             null=True,
                             blank=True)
    vistoria = models.DateField(null=True, blank=True)
    usuario = models.ForeignKey(User, related_name='area_soltura')
    data_criacao = models.DateTimeField('Data de Criação', auto_now_add=True)
    geom = models.PolygonField(srid=4674)
    objects = models.GeoManager()

    def __str__(self):
        return '%s' % self.processo

    class Meta:
        verbose_name = 'Área de Soltura de Animais Silvestres'
        verbose_name_plural = 'Áreas de Soltura de Animais Silvestres'
Exemplo n.º 22
0
class AbstractObservation(models.Model):
    originates_in_vespawatch = models.BooleanField(
        default=True,
        help_text=
        "The observation was first created in VespaWatch, not iNaturalist")
    taxon = models.ForeignKey(Taxon,
                              on_delete=models.PROTECT,
                              blank=True,
                              null=True)
    observation_time = models.DateTimeField(verbose_name=_("Observation date"),
                                            validators=[no_future])
    comments = models.TextField(
        verbose_name=_("Comments"),
        blank=True,
        help_text=
        _("Comments are public: use them to describe your observation and help verification."
          ))

    latitude = models.FloatField(
        validators=[MinValueValidator(-90),
                    MaxValueValidator(90)],
        verbose_name=_("Latitude"))
    longitude = models.FloatField(
        validators=[MinValueValidator(-180),
                    MaxValueValidator(180)],
        verbose_name=_("Longitude"))

    inaturalist_id = models.BigIntegerField(verbose_name=_("iNaturalist ID"),
                                            blank=True,
                                            null=True)
    inaturalist_species = models.CharField(
        verbose_name=_("iNaturalist species"),
        max_length=100,
        blank=True,
        null=True)  # TODO: check if this is still in use or useful
    inat_vv_confirmed = models.BooleanField(
        blank=True,
        null=True)  # The community ID of iNaturalist says it's Vespa Velutina

    # Observer info
    observer_name = models.CharField(verbose_name=_("Name"),
                                     max_length=255,
                                     blank=True,
                                     null=True)
    observer_email = models.EmailField(verbose_name=_("Email address"),
                                       blank=True,
                                       null=True)
    observer_phone = models.CharField(verbose_name=_("Telephone number"),
                                      max_length=20,
                                      blank=True,
                                      null=True)

    created_at = models.DateTimeField(default=timezone.now)

    # Managers
    objects = models.Manager()  # The default manager.
    from_inat_objects = InatCreatedObservationsManager()
    from_vespawatch_objects = VespawatchCreatedObservationsManager()
    new_vespawatch_objects = VespawatchNewlyCreatedObservationsManager()

    class Meta:
        abstract = True
        # We got some duplicates and don't exactly know why, this is an attempt to block them without being too
        # aggresive and introduce bugs (hence the limited number of fields).
        unique_together = [
            'taxon', 'observation_time', 'latitude', 'longitude', 'comments',
            'inaturalist_id'
        ]

    @property
    def vernacular_names_in_all_languages(self):
        """Returns a dict such as: {'en': XXXX, 'nl': YYYY}"""
        vn = {}
        for lang in settings.LANGUAGES:
            code = lang[0]
            vn[code] = getattr(self.taxon, f'vernacular_name_{code}')
        return vn

    @property
    def display_vernacular_name(self):
        if self.taxon:
            return _(self.taxon.vernacular_name)
        else:
            return ''

    @property
    def display_scientific_name(self):
        if self.taxon:
            return self.taxon.name
        else:
            return self.inaturalist_species or _('Unknown')

    @property
    def can_be_edited_in_admin(self):
        if self.originates_in_vespawatch:
            if self.exists_in_inaturalist:
                return False
            else:
                return True
        else:  # Comes from iNaturalist: we can never delete
            return False

    @property
    def can_be_edited_or_deleted(self):
        """Return True if this observation can be edited in Vespa-Watch (admin, ...)"""
        return self.originates_in_vespawatch  # We can't edit obs that comes from iNaturalist (they're never pushed).

    @property
    def taxon_can_be_locally_changed(self):
        if self.originates_in_vespawatch and self.exists_in_inaturalist:
            return False  # Because we rely on community: info is always pulled and never pushed

        return True

    @property
    def exists_in_inaturalist(self):
        return self.inaturalist_id is not None

    @property
    def inaturalist_obs_url(self):
        if self.exists_in_inaturalist:
            return f'https://www.inaturalist.org/observations/{self.inaturalist_id}'

        return None

    def has_warnings(self):
        return len(self.warnings.all()) > 0

    has_warnings.boolean = True

    def _params_for_inat(self):
        """(Create/update): Common ground for the pushed data to iNaturalist.

        taxon_id is not part of it because we rely on iNaturalist to correct the identification, if necessary.
        All the rest is pushed.
        """

        vespawatch_evidence_value = 'nest' if self.__class__ == Nest else 'individual'

        ofv = [{
            'observation_field_id': settings.VESPAWATCH_ID_OBS_FIELD_ID,
            'value': self.pk
        }, {
            'observation_field_id': settings.VESPAWATCH_EVIDENCE_OBS_FIELD_ID,
            'value': vespawatch_evidence_value
        }]

        if vespawatch_evidence_value == 'individual' and self.behaviour:
            ofv.append(
                {
                    'observation_field_id':
                    settings.VESPAWATCH_BEHAVIOUR_OBS_FIELD_ID,
                    'value': self.get_behaviour_display()
                }
            )  # TODO: get_behaviour_display(): what will happen to push if we translate the values for the UI

        return {
            'observed_on_string':
            self.observation_time.isoformat(),
            'time_zone':
            'Brussels',
            'description':
            self.comments,
            'latitude':
            self.latitude,
            'longitude':
            self.longitude,
            'observation_field_values_attributes': [{
                'observation_field_id':
                settings.VESPAWATCH_ID_OBS_FIELD_ID,
                'value':
                self.pk
            }, {
                'observation_field_id':
                settings.VESPAWATCH_EVIDENCE_OBS_FIELD_ID,
                'value':
                vespawatch_evidence_value
            }]
        }

    def flag_warning(self, text):
        if text in [x.text for x in self.warnings.all()]:
            return  # warning already set
        if self.__class__.__name__ == 'Nest':
            warning = NestObservationWarning(text=text,
                                             datetime=now(),
                                             observation=self)
            warning.save()
        elif self.__class__.__name__ == 'Individual':
            warning = IndividualObservationWarning(text=text,
                                                   datetime=now(),
                                                   observation=self)
            warning.save()

    def flag_based_on_inat_data(self, inat_observation_data):
        """
        The observation was no longer found on iNaturalist with our general filters.
        Check why, and flag this observation
        """
        # Project is vespawatch?
        if not settings.VESPAWATCH_PROJECT_ID in inat_observation_data[
                'project_ids']:
            self.flag_warning('not in vespawatch project')

        # Taxon known in VW?
        returned_taxon_id = ''
        if 'community_taxon_id' in inat_observation_data and inat_observation_data[
                'community_taxon_id']:
            returned_taxon_id = inat_observation_data['community_taxon_id']
        elif 'taxon' in inat_observation_data:
            if 'id' in inat_observation_data['taxon']:
                returned_taxon_id = inat_observation_data['taxon']['id']
        if returned_taxon_id not in [
                y for x in Taxon.objects.all()
                for y in x.inaturalist_pull_taxon_ids
        ]:
            self.flag_warning('unknown taxon')

    def update_from_inat_data(self, inat_observation_data):
        # Check the vespawatch_evidence
        # ------
        # If the observation is a nest but the vespawatch evidence is not nest => flag the nest
        if 'ofvs' in inat_observation_data:
            vw_evidence_list = [
                x['value'] for x in inat_observation_data['ofvs']
                if x['field_id'] == settings.VESPAWATCH_EVIDENCE_OBS_FIELD_ID
            ]
            if len(vw_evidence_list) > 0:
                vw_evidence = vw_evidence_list[0]

                if self.__class__.__name__ == 'Nest':
                    if vw_evidence != 'nest':
                        self.flag_warning('individual at inaturalist')
                # If the observation is an individual but the vespawatch evidence is a nest and the observation originates in vespawatch => delete the individual and create a nest
                elif self.__class__.__name__ == 'Individual':
                    if vw_evidence == 'nest':
                        if self.originates_in_vespawatch:
                            self.flag_warning('nest at inaturalist')
                        else:
                            create_observation_from_inat_data(
                                inat_observation_data)
                            self.delete()
                            return

        # Update taxon data and set inat_vv_confirmed (use inat_data_confirms_vv() )
        self.inat_vv_confirmed = inat_data_confirms_vv(inat_observation_data)

        # Update photos
        # -------------
        # When we pull again and the API returns additional images, those are not added. This is done
        # because we insert a UUID in the filename when we pull it. The result of that is that we cannot
        # compare that image with the image url that we retrieve from iNaturalist. So to prevent adding
        # the same image again and again with subsequent pulls, we only add images when the observation
        # has none.
        if len(self.pictures.all()) == 0:
            for photo in inat_observation_data['photos']:
                self.assign_picture_from_url(photo['url'])

        # Update location
        self.latitude = inat_observation_data['geojson']['coordinates'][1]
        self.longitude = inat_observation_data['geojson']['coordinates'][0]

        # Update time
        # -------------
        observation_time = dateparser.parse(
            inat_observation_data['observed_on_string'],
            settings={'TIMEZONE': inat_observation_data['observed_time_zone']})
        if observation_time is None:
            # Sometimes, dateparser doesn't understand the string but we have the bits and pieces in
            # inaturalist_data['observed_on_details']
            details = inat_observation_data['observed_on_details']
            observation_time = datetime(
                year=details['year'],
                month=details['month'],
                day=details['day'],
                hour=details['hour']
            )  # in the observed cases, we had nothing more precise than the hour

        # Sometimes, the time is naive (even when specifying it to dateparser), because (for the detected cases, at least)
        # The time is 00:00:00. In that case we make it aware to avoid Django warnings (in the local time zone since all
        # observations occur in Belgium
        if is_naive(observation_time):
            # Some dates (apparently)
            observation_time = make_aware(observation_time)

        self.observation_time = observation_time

        self.comments = inat_observation_data['description'] or ''

        # Update taxon
        # -------------
        try:
            self.inaturalist_species = ''
            taxon = get_taxon_from_inat_taxon_id(
                inat_observation_data['taxon']['id'])
            self.taxon = taxon
        except Taxon.DoesNotExist:
            self.taxon = None
            self.inaturalist_species = inat_observation_data['taxon'][
                'name'] if 'name' in inat_observation_data['taxon'] else ''

        self.save()

    def create_at_inaturalist(self, access_token, user_agent):
        """Creates a new observation at iNaturalist for this observation

        It will update the current object so self.inaturalist_id is properly set.
        On the other side, it will also set the vespawatch_id observation field so the observation can be found from
        the iNaturalist record.

        :param access_token: as returned by pyinaturalist.rest_api.get_access_token(
        """

        params_only_for_create = {
            'taxon_id': self.taxon.inaturalist_push_taxon_id
        }  # TODO: with the new sync, does it still makes sense to separate the create/update parameters?

        params = {
            'observation': {
                **params_only_for_create,
                **self._params_for_inat()
            }
        }

        r = create_observations(params=params,
                                access_token=access_token,
                                user_agent=user_agent)
        self.inaturalist_id = r[0]['id']
        self.save()
        self.push_attached_pictures_at_inaturalist(access_token=access_token,
                                                   user_agent=user_agent)

    def get_photo_filename(self, photo_url):
        # TODO: Find a cleaner solution to this
        # It seems the iNaturalist only returns small thumbnails such as
        # 'https://static.inaturalist.org/photos/1960816/square.jpg?1444437211'
        # We can circumvent the issue by hacking the URL...
        photo_url = photo_url.replace('square.jpg', 'large.jpg')
        photo_url = photo_url.replace('square.jpeg', 'large.jpeg')
        photo_filename = photo_url[photo_url.rfind("/") + 1:].split('?', 1)[0]
        return photo_filename

    def assign_picture_from_url(self, photo_url):
        photo_filename = self.get_photo_filename(photo_url)
        if photo_filename not in [x.image.name for x in self.pictures.all()]:
            if self.__class__ == Nest:
                photo_obj = NestPicture()
            else:
                photo_obj = IndividualPicture()

            photo_content = ContentFile(requests.get(photo_url).content)

            photo_obj.observation = self
            photo_obj.image.save(photo_filename, photo_content)
            photo_obj.save()

    def push_attached_pictures_at_inaturalist(self, access_token, user_agent):
        if self.inaturalist_id:
            for picture in self.pictures.all():
                add_photo_to_observation(observation_id=self.inaturalist_id,
                                         file_object=picture.image.read(),
                                         access_token=access_token,
                                         user_agent=user_agent)

    def get_taxon_name(self):
        if self.taxon:
            return self.taxon.name
        else:
            return ''

    @property
    def formatted_observation_date(self):
        # We need to be aware of the timezone, hence the defaultfilter trick
        return defaultfilters.date(self.observation_time, 'Y-m-d')

    @property
    def observation_time_iso(self):
        return self.observation_time.isoformat()

    def save(self, *args, **kwargs):
        # Let's make sure model.clean() is called on each save(), for validation
        self.full_clean()

        return super(AbstractObservation, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        if self.originates_in_vespawatch and self.exists_in_inaturalist:
            InatObsToDelete.objects.create(inaturalist_id=self.inaturalist_id)

        return super(AbstractObservation, self).delete(*args, **kwargs)
Exemplo n.º 23
0
class Event(MPTTModel, BaseModel, SchemalessFieldMixin, ReplacedByMixin):
    jsonld_type = "Event/LinkedEvent"
    objects = BaseTreeQuerySet.as_manager()
    """
    eventStatus enumeration is based on http://schema.org/EventStatusType
    """
    class Status:
        SCHEDULED = 1
        CANCELLED = 2
        POSTPONED = 3
        RESCHEDULED = 4

    # Properties from schema.org/Event
    STATUSES = (
        (Status.SCHEDULED, "EventScheduled"),
        (Status.CANCELLED, "EventCancelled"),
        (Status.POSTPONED, "EventPostponed"),
        (Status.RESCHEDULED, "EventRescheduled"),
    )

    class SuperEventType:
        RECURRING = 'recurring'
        UMBRELLA = 'umbrella'

    SUPER_EVENT_TYPES = (
        (SuperEventType.RECURRING, _('Recurring')),
        (SuperEventType.UMBRELLA, _('Umbrella event')),
    )

    # Properties from schema.org/Thing
    info_url = models.URLField(verbose_name=_('Event home page'),
                               blank=True,
                               null=True,
                               max_length=1000)
    description = models.TextField(verbose_name=_('Description'),
                                   blank=True,
                                   null=True)
    short_description = models.TextField(verbose_name=_('Short description'),
                                         blank=True,
                                         null=True)

    # Properties from schema.org/CreativeWork
    date_published = models.DateTimeField(verbose_name=_('Date published'),
                                          null=True,
                                          blank=True)
    # headline and secondary_headline are for cases where
    # the original event data contains a title and a subtitle - in that
    # case the name field is combined from these.
    #
    # secondary_headline is mapped to schema.org alternative_headline
    # and is used for subtitles, that is for
    # secondary, complementary headlines, not "alternative" headlines
    headline = models.CharField(verbose_name=_('Headline'),
                                max_length=255,
                                null=True,
                                db_index=True)
    secondary_headline = models.CharField(verbose_name=_('Secondary headline'),
                                          max_length=255,
                                          null=True,
                                          db_index=True)
    provider = models.CharField(verbose_name=_('Provider'),
                                max_length=512,
                                null=True)
    provider_contact_info = models.CharField(
        verbose_name=_("Provider's contact info"),
        max_length=255,
        null=True,
        blank=True)
    publisher = models.ForeignKey('django_orghierarchy.Organization',
                                  verbose_name=_('Publisher'),
                                  db_index=True,
                                  on_delete=models.PROTECT,
                                  related_name='published_events',
                                  null=True)

    # Status of the event itself
    event_status = models.SmallIntegerField(verbose_name=_('Event status'),
                                            choices=STATUSES,
                                            default=Status.SCHEDULED)

    # Whether or not this data about the event is ready to be viewed by the general public.
    # DRAFT means the data is considered incomplete or is otherwise undergoing refinement --
    # or just waiting to be published for other reasons.
    publication_status = models.SmallIntegerField(
        verbose_name=_('Event data publication status'),
        choices=PUBLICATION_STATUSES,
        default=PublicationStatus.PUBLIC)

    location = models.ForeignKey(Place,
                                 related_name='events',
                                 null=True,
                                 blank=True,
                                 on_delete=models.PROTECT)
    location_extra_info = models.CharField(
        verbose_name=_('Location extra info'),
        max_length=400,
        null=True,
        blank=True)

    start_time = models.DateTimeField(verbose_name=_('Start time'),
                                      null=True,
                                      db_index=True,
                                      blank=True)
    end_time = models.DateTimeField(verbose_name=_('End time'),
                                    null=True,
                                    db_index=True,
                                    blank=True)
    has_start_time = models.BooleanField(default=True)
    has_end_time = models.BooleanField(default=True)

    audience_min_age = models.SmallIntegerField(
        verbose_name=_('Minimum recommended age'),
        blank=True,
        null=True,
        db_index=True)
    audience_max_age = models.SmallIntegerField(
        verbose_name=_('Maximum recommended age'),
        blank=True,
        null=True,
        db_index=True)

    super_event = TreeForeignKey('self',
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL,
                                 related_name='sub_events')

    super_event_type = models.CharField(max_length=255,
                                        blank=True,
                                        null=True,
                                        db_index=True,
                                        default=None,
                                        choices=SUPER_EVENT_TYPES)

    in_language = models.ManyToManyField(Language,
                                         verbose_name=_('In language'),
                                         related_name='events',
                                         blank=True)

    images = models.ManyToManyField(Image, related_name='events', blank=True)

    deleted = models.BooleanField(default=False, db_index=True)

    replaced_by = models.ForeignKey('Event',
                                    on_delete=models.SET_NULL,
                                    related_name='aliases',
                                    null=True,
                                    blank=True)

    # Custom fields not from schema.org
    keywords = models.ManyToManyField(Keyword, related_name='events')
    audience = models.ManyToManyField(Keyword,
                                      related_name='audience_events',
                                      blank=True)

    # Tavastia Events
    pin = models.CharField(blank=False, max_length=64, default='0000')
    accessible = models.BooleanField(default=False, null=False, blank=False)
    provider_email = models.EmailField(null=True,
                                       blank=True,
                                       default='*****@*****.**')
    multi_day = models.BooleanField(default=False, null=False)

    class Meta:
        verbose_name = _('event')
        verbose_name_plural = _('events')

    class MPTTMeta:
        parent_attr = 'super_event'

    def save(self, *args, **kwargs):
        if self._has_circular_replacement():
            raise Exception(
                "Trying to replace this event with an event that is replaced by this event"
                "Please refrain from creating circular replacements and"
                "remove one of the replacements.")

        if self.replaced_by and not self.deleted:
            self.deleted = True
            logger.warning(
                "Event replaced without soft deleting. Soft deleting automatically",
                extra={'event': self})

        # needed to cache location event numbers
        old_location = None

        # needed for notifications
        old_publication_status = None
        old_deleted = None
        created = True

        if self.id:
            try:
                event = Event.objects.get(id=self.id)
                created = False
                old_location = event.location
                old_publication_status = event.publication_status
                old_deleted = event.deleted
            except Event.DoesNotExist:
                pass

        # drafts may not have times set, so check that first
        start = getattr(self, 'start_time', None)
        end = getattr(self, 'end_time', None)
        if start and end:
            if start > end:
                raise ValidationError({
                    'end_time':
                    _('The event end time cannot be earlier than the start time.'
                      )
                })

        if (self.keywords.filter(deprecated=True) or
                self.audience.filter(deprecated=True)) and (not self.deleted):
            raise ValidationError({
                'keywords':
                _("Trying to save event with deprecated keywords " +
                  str(self.keywords.filter(deprecated=True).values('id')) +
                  " or " +
                  str(self.audience.filter(deprecated=True).values('id')) +
                  ". Please use up-to-date keywords.")
            })

        super(Event, self).save(*args, **kwargs)

        # needed to cache location event numbers
        if not old_location and self.location:
            Place.objects.filter(id=self.location.id).update(
                n_events_changed=True)
        if old_location and not self.location:
            # drafts (or imported events) may not always have location set
            Place.objects.filter(id=old_location.id).update(
                n_events_changed=True)
        if old_location and self.location and old_location != self.location:
            Place.objects.filter(id__in=(old_location.id,
                                         self.location.id)).update(
                                             n_events_changed=True)
            call_command('update_n_events')

        # send notifications
        if old_publication_status == PublicationStatus.DRAFT and self.publication_status == PublicationStatus.PUBLIC:
            self.send_published_notification()
        if old_deleted is False and self.deleted is True:
            self.send_deleted_notification()
        if created and self.publication_status == PublicationStatus.DRAFT:
            self.send_draft_posted_notification()

    def __str__(self):
        name = ''
        languages = [lang[0] for lang in settings.LANGUAGES]
        for lang in languages:
            lang = lang.replace(
                '-', '_')  # to handle complex codes like e.g. zh-hans
            s = getattr(self, 'name_%s' % lang, None)
            if s:
                name = s
                break
        val = [name, '(%s)' % self.id]
        dcount = self.get_descendant_count()
        if dcount > 0:
            val.append(u" (%d children)" % dcount)
        else:
            val.append(str(self.start_time))
        return u" ".join(val)

    def is_admin(self, user):
        if user.is_superuser:
            return True
        else:
            return user.is_admin(self.publisher)

    def can_be_edited_by(self, user):
        """Check if current event can be edited by the given user"""
        if user.is_superuser:
            return True
        return user.can_edit_event(self.publisher, self.publication_status)

    def soft_delete(self, using=None):
        self.deleted = True
        self.save(update_fields=("deleted", ), using=using, force_update=True)

    def undelete(self, using=None):
        self.deleted = False
        self.save(update_fields=("deleted", ), using=using, force_update=True)

    def _send_notification(self,
                           notification_type,
                           recipient_list,
                           request=None):
        if len(recipient_list) == 0:
            logger.warning("No recipients for notification type '%s'" %
                           notification_type,
                           extra={'event': self})
            return
        context = {'event': self}
        try:
            rendered_notification = render_notification_template(
                notification_type, context)
        except NotificationTemplateException as e:
            logger.error(e, exc_info=True, extra={'request': request})
            return
        try:
            send_mail(rendered_notification['subject'],
                      rendered_notification['body'],
                      'noreply@%s' % Site.objects.get_current().domain,
                      recipient_list,
                      html_message=rendered_notification['html_body'])
        except SMTPException as e:
            logger.error(e,
                         exc_info=True,
                         extra={
                             'request': request,
                             'event': self
                         })

    def _get_author_emails(self):
        author_emails = []
        for user in (self.created_by, self.last_modified_by):
            if user and user.email:
                author_emails.append(user.email)
        return author_emails

    def send_deleted_notification(self, request=None):
        recipient_list = self._get_author_emails()
        self._send_notification(NotificationType.UNPUBLISHED_EVENT_DELETED,
                                recipient_list, request)

    def send_published_notification(self, request=None):
        recipient_list = self._get_author_emails()
        self._send_notification(NotificationType.EVENT_PUBLISHED,
                                recipient_list, request)

    def send_draft_posted_notification(self, request=None):
        recipient_list = []
        for admin in self.publisher.admin_users.all():
            if admin.email:
                recipient_list.append(admin.email)
        self._send_notification(NotificationType.DRAFT_POSTED, recipient_list,
                                request)

    # Tavastia Events
    # Filter soft deleted events from events sub events
    def filter_deleted(self):
        return self.sub_events.filter(deleted=False)
Exemplo n.º 24
0
class Reservation(ModifiableModel):
    CREATED = 'created'
    CANCELLED = 'cancelled'
    CONFIRMED = 'confirmed'
    DENIED = 'denied'
    REQUESTED = 'requested'
    WAITING_FOR_PAYMENT = 'waiting_for_payment'
    STATE_CHOICES = (
        (CREATED, _('created')),
        (CANCELLED, _('cancelled')),
        (CONFIRMED, _('confirmed')),
        (DENIED, _('denied')),
        (REQUESTED, _('requested')),
        (WAITING_FOR_PAYMENT, _('waiting for payment')),
    )

    TYPE_NORMAL = 'normal'
    TYPE_BLOCKED = 'blocked'
    TYPE_CHOICES = (
        (TYPE_NORMAL, _('Normal reservation')),
        (TYPE_BLOCKED, _('Resource blocked')),
    )

    resource = models.ForeignKey('Resource',
                                 verbose_name=_('Resource'),
                                 db_index=True,
                                 related_name='reservations',
                                 on_delete=models.PROTECT)
    begin = models.DateTimeField(verbose_name=_('Begin time'))
    end = models.DateTimeField(verbose_name=_('End time'))
    duration = pgfields.DateTimeRangeField(
        verbose_name=_('Length of reservation'),
        null=True,
        blank=True,
        db_index=True)
    comments = models.TextField(null=True,
                                blank=True,
                                verbose_name=_('Comments'))
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             verbose_name=_('User'),
                             null=True,
                             blank=True,
                             db_index=True,
                             on_delete=models.PROTECT)

    preferred_language = models.CharField(choices=settings.LANGUAGES,
                                          verbose_name='Preferred Language',
                                          null=True,
                                          default=settings.LANGUAGES[0][0],
                                          max_length=8)

    state = models.CharField(max_length=32,
                             choices=STATE_CHOICES,
                             verbose_name=_('State'),
                             default=CREATED)
    approver = models.ForeignKey(settings.AUTH_USER_MODEL,
                                 verbose_name=_('Approver'),
                                 related_name='approved_reservations',
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL)
    staff_event = models.BooleanField(verbose_name=_('Is staff event'),
                                      default=False)
    type = models.CharField(blank=False,
                            verbose_name=_('Type'),
                            max_length=32,
                            choices=TYPE_CHOICES,
                            default=TYPE_NORMAL)

    has_arrived = models.BooleanField(verbose_name=_('Has arrived'),
                                      default=False)

    # access-related fields
    access_code = models.CharField(verbose_name=_('Access code'),
                                   max_length=32,
                                   null=True,
                                   blank=True)

    # EXTRA FIELDS START HERE

    event_subject = models.CharField(max_length=200,
                                     verbose_name=_('Event subject'),
                                     blank=True)
    event_description = models.TextField(verbose_name=_('Event description'),
                                         blank=True)
    number_of_participants = models.PositiveSmallIntegerField(
        verbose_name=_('Number of participants'),
        blank=True,
        null=True,
        default=1)
    participants = models.TextField(verbose_name=_('Participants'), blank=True)
    host_name = models.CharField(verbose_name=_('Host name'),
                                 max_length=100,
                                 blank=True)
    require_assistance = models.BooleanField(
        verbose_name=_('Require assistance'), default=False)
    require_workstation = models.BooleanField(
        verbose_name=_('Require workstation'), default=False)
    home_municipality = models.ForeignKey('ReservationHomeMunicipalityField',
                                          verbose_name=_('Home municipality'),
                                          null=True,
                                          blank=True,
                                          on_delete=models.SET_NULL)

    # extra detail fields for manually confirmed reservations

    reserver_name = models.CharField(verbose_name=_('Reserver name'),
                                     max_length=100,
                                     blank=True)
    reserver_id = models.CharField(
        verbose_name=_('Reserver ID (business or person)'),
        max_length=30,
        blank=True)
    reserver_email_address = models.EmailField(
        verbose_name=_('Reserver email address'), blank=True)
    reserver_phone_number = models.CharField(
        verbose_name=_('Reserver phone number'), max_length=30, blank=True)
    reserver_address_street = models.CharField(
        verbose_name=_('Reserver address street'), max_length=100, blank=True)
    reserver_address_zip = models.CharField(
        verbose_name=_('Reserver address zip'), max_length=30, blank=True)
    reserver_address_city = models.CharField(
        verbose_name=_('Reserver address city'), max_length=100, blank=True)
    reservation_extra_questions = models.TextField(
        verbose_name=_('Reservation extra questions'), blank=True)

    company = models.CharField(verbose_name=_('Company'),
                               max_length=100,
                               blank=True)
    billing_first_name = models.CharField(verbose_name=_('Billing first name'),
                                          max_length=100,
                                          blank=True)
    billing_last_name = models.CharField(verbose_name=_('Billing last name'),
                                         max_length=100,
                                         blank=True)
    billing_email_address = models.EmailField(
        verbose_name=_('Billing email address'), blank=True)
    billing_phone_number = models.CharField(
        verbose_name=_('Billing phone number'), max_length=30, blank=True)
    billing_address_street = models.CharField(
        verbose_name=_('Billing address street'), max_length=100, blank=True)
    billing_address_zip = models.CharField(
        verbose_name=_('Billing address zip'), max_length=30, blank=True)
    billing_address_city = models.CharField(
        verbose_name=_('Billing address city'), max_length=100, blank=True)

    # If the reservation was imported from another system, you can store the original ID in the field below.
    origin_id = models.CharField(verbose_name=_('Original ID'),
                                 max_length=50,
                                 editable=False,
                                 null=True)

    reminder = models.ForeignKey('ReservationReminder',
                                 verbose_name=_('Reservation Reminder'),
                                 db_index=True,
                                 related_name='ReservationReminders',
                                 on_delete=models.SET_NULL,
                                 null=True,
                                 blank=True)

    timmi_id = models.PositiveIntegerField(verbose_name=_('Timmi ID'),
                                           null=True,
                                           blank=True)
    timmi_receipt = models.TextField(verbose_name=_('Timmi receipt'),
                                     null=True,
                                     blank=True,
                                     max_length=2000)

    objects = ReservationQuerySet.as_manager()

    class Meta:
        verbose_name = _("reservation")
        verbose_name_plural = _("reservations")
        ordering = ('id', )

    def _save_dt(self, attr, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        save_dt(self, attr, dt, self.resource.unit.time_zone)

    def _get_dt(self, attr, tz):
        return get_dt(self, attr, tz)

    @property
    def begin_tz(self):
        return self.begin

    @begin_tz.setter
    def begin_tz(self, dt):
        self._save_dt('begin', dt)

    def get_begin_tz(self, tz):
        return self._get_dt("begin", tz)

    @property
    def end_tz(self):
        return self.end

    @end_tz.setter
    def end_tz(self, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        self._save_dt('end', dt)

    def get_end_tz(self, tz):
        return self._get_dt("end", tz)

    def is_active(self):
        print(
            self.end + self.resource.cooldown >= timezone.now()
            and self.state not in (Reservation.CANCELLED, Reservation.DENIED))
        return self.end + self.resource.cooldown >= timezone.now(
        ) and self.state not in (Reservation.CANCELLED, Reservation.DENIED)

    def is_own(self, user):
        if not (user and user.is_authenticated):
            return False
        return user == self.user

    def need_manual_confirmation(self):
        return self.resource.need_manual_confirmation

    def are_extra_fields_visible(self, user):
        # the following logic is used also implemented in ReservationQuerySet
        # so if this is changed that probably needs to be changed as well

        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_access_code(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_access_code(user)

    def set_state(self, new_state, user):
        # Make sure it is a known state
        assert new_state in (Reservation.REQUESTED, Reservation.CONFIRMED,
                             Reservation.DENIED, Reservation.CANCELLED,
                             Reservation.WAITING_FOR_PAYMENT)
        old_state = self.state
        if new_state == old_state:
            if old_state == Reservation.CONFIRMED:
                reservation_modified.send(sender=self.__class__,
                                          instance=self,
                                          user=user)
            return
        if new_state == Reservation.CONFIRMED:
            self.approver = user if user and user.is_authenticated else None
            if user and user.is_authenticated or self.resource.authentication == 'unauthenticated':
                reservation_confirmed.send(sender=self.__class__,
                                           instance=self,
                                           user=user)
        elif old_state == Reservation.CONFIRMED:
            self.approver = None

        user_is_staff = self.user is not None and self.user.is_staff

        # Notifications
        if new_state == Reservation.REQUESTED:
            if not user_is_staff:
                self.send_reservation_requested_mail()
                self.notify_staff_about_reservation(
                    NotificationType.RESERVATION_REQUESTED_OFFICIAL)
            else:
                if self.reserver_email_address != self.user.email:
                    self.send_reservation_requested_mail(
                        action_by_official=True)
        elif new_state == Reservation.CONFIRMED:
            if self.need_manual_confirmation():
                self.send_reservation_confirmed_mail()
            elif self.access_code:
                if not user_is_staff:
                    self.send_reservation_created_with_access_code_mail()
                    self.notify_staff_about_reservation(
                        NotificationType.
                        RESERVATION_CREATED_WITH_ACCESS_CODE_OFFICIAL)
                else:
                    if self.reserver_email_address != self.user.email:
                        self.send_reservation_created_with_access_code_mail(
                            action_by_official=True)
            else:
                if not user_is_staff:
                    self.send_reservation_created_mail()
                    self.notify_staff_about_reservation(
                        NotificationType.RESERVATION_CREATED_OFFICIAL)
                else:
                    if self.reserver_email_address != self.user.email:
                        self.send_reservation_created_mail(
                            action_by_official=True)
                        self.notify_staff_about_reservation(
                            NotificationType.RESERVATION_CREATED_OFFICIAL)
        elif new_state == Reservation.DENIED:
            self.send_reservation_denied_mail()
        elif new_state == Reservation.CANCELLED:
            if self.user:
                order = self.get_order()
                if order:
                    if order.state == order.CANCELLED:
                        self.send_reservation_cancelled_mail()
                else:
                    if user.is_staff and (user.email != self.user.email
                                          ):  # Assume staff cancelled it
                        self.send_reservation_cancelled_mail(
                            action_by_official=True)
                    else:
                        self.send_reservation_cancelled_mail()
                        self.notify_staff_about_reservation(
                            NotificationType.RESERVATION_CANCELLED_OFFICIAL)
            reservation_cancelled.send(sender=self.__class__,
                                       instance=self,
                                       user=user)
        self.state = new_state
        self.save()

    def can_modify(self, user):
        if not user:
            return False

        if self.state == Reservation.WAITING_FOR_PAYMENT:
            return False

        if self.get_order():
            return self.resource.can_modify_paid_reservations(user)

        # reservations that need manual confirmation and are confirmed cannot be
        # modified or cancelled without reservation approve permission
        cannot_approve = not self.resource.can_approve_reservations(user)
        if self.need_manual_confirmation(
        ) and self.state == Reservation.CONFIRMED and cannot_approve:
            return False

        return self.user == user or self.resource.can_modify_reservations(user)

    def can_add_comment(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_access_reservation_comments(user)

    def can_view_field(self, user, field):
        if field not in RESERVATION_EXTRA_FIELDS:
            return True
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_catering_orders(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_catering_orders(user)

    def can_add_product_order(self, user):
        return self.is_own(user)

    def can_view_product_orders(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_product_orders(user)

    def get_order(self):
        return getattr(self, 'order', None)

    def format_time(self):
        tz = self.resource.unit.get_tz()
        begin = self.begin.astimezone(tz)
        end = self.end.astimezone(tz)
        return format_dt_range(translation.get_language(), begin, end)

    def create_reminder(self):
        r_date = self.begin - datetime.timedelta(
            hours=int(self.resource.unit.sms_reminder_delay))
        reminder = ReservationReminder()
        reminder.reservation = self
        reminder.reminder_date = r_date
        reminder.save()
        self.reminder = reminder

    def modify_reminder(self):
        if not self.reminder:
            return
        r_date = self.begin - datetime.timedelta(
            hours=int(self.resource.unit.sms_reminder_delay))
        self.reminder.reminder_date = r_date
        self.reminder.save()

    def __str__(self):
        if self.state != Reservation.CONFIRMED:
            state_str = ' (%s)' % self.state
        else:
            state_str = ''
        return "%s: %s%s" % (self.format_time(), self.resource, state_str)

    def clean(self, **kwargs):
        """
        Check restrictions that are common to all reservations.

        If this reservation isn't yet saved and it will modify an existing reservation,
        the original reservation need to be provided in kwargs as 'original_reservation', so
        that it can be excluded when checking if the resource is available.
        """

        if 'user' in kwargs:
            user = kwargs['user']
        else:
            user = self.user

        user_is_admin = user and self.resource.is_admin(user)

        if self.end <= self.begin:
            raise ValidationError(
                _("You must end the reservation after it has begun"))

        # Check that begin and end times are on valid time slots.
        opening_hours = self.resource.get_opening_hours(
            self.begin.date(), self.end.date())
        for dt in (self.begin, self.end):
            days = opening_hours.get(dt.date(), [])
            day = next((day for day in days if day['opens'] is not None
                        and day['opens'] <= dt <= day['closes']), None)
            if day and not is_valid_time_slot(dt, self.resource.slot_size,
                                              day['opens']):
                raise ValidationError(
                    _("Begin and end time must match time slots"),
                    code='invalid_time_slot')

        # Check if Unit has disallow_overlapping_reservations value of True
        if (self.resource.unit.disallow_overlapping_reservations and
                not self.resource.can_create_overlapping_reservations(user)):
            if self.resource.unit.disallow_overlapping_reservations_per_user:
                reservations_for_same_unit = Reservation.objects.filter(
                    user=user, resource__unit=self.resource.unit)
            else:
                reservations_for_same_unit = Reservation.objects.filter(
                    resource__unit=self.resource.unit)
            valid_reservations_for_same_unit = reservations_for_same_unit.exclude(
                state=Reservation.CANCELLED)
            user_has_conflicting_reservations = valid_reservations_for_same_unit.filter(
                Q(begin__gt=self.begin, begin__lt=self.end)
                | Q(begin__lt=self.begin, end__gt=self.begin)
                | Q(begin__gte=self.begin, end__lte=self.end))

            if user_has_conflicting_reservations:
                raise ValidationError(_(
                    'This unit does not allow overlapping reservations for its resources'
                ),
                                      code='conflicting_reservation')

        original_reservation = self if self.pk else kwargs.get(
            'original_reservation', None)
        if self.resource.check_reservation_collision(self.begin, self.end,
                                                     original_reservation):
            raise ValidationError(
                _("The resource is already reserved for some of the period"))

        if not user_is_admin:
            if (self.end - self.begin) < self.resource.min_period:
                raise ValidationError(
                    _("The minimum reservation length is %(min_period)s") % {
                        'min_period': humanize_duration(
                            self.resource.min_period)
                    })
        else:
            if not (self.end - self.begin
                    ) % self.resource.slot_size == datetime.timedelta(0):
                raise ValidationError(
                    _("The minimum reservation length is %(slot_size)s") %
                    {'slot_size': humanize_duration(self.resource.slot_size)})

        if self.access_code:
            validate_access_code(self.access_code,
                                 self.resource.access_code_type)

        if self.resource.people_capacity:
            if (self.number_of_participants > self.resource.people_capacity):
                raise ValidationError(
                    _("This resource has people capacity limit of %s" %
                      self.resource.people_capacity))

    def get_notification_context(self,
                                 language_code,
                                 user=None,
                                 notification_type=None,
                                 extra_context={}):
        if not user:
            user = self.user
        with translation.override(language_code):
            reserver_home_municipality = self.home_municipality_id
            for municipality in self.resource.get_included_home_municipality_names(
            ):
                if municipality['id'] == self.home_municipality_id:
                    reserver_home_municipality = municipality['name'].get(
                        language_code, None)
                    break

            reserver_name = self.reserver_name
            reserver_email_address = self.reserver_email_address
            if not reserver_name and self.user and self.user.get_display_name(
            ):
                reserver_name = self.user.get_display_name()
            if not reserver_email_address and user and user.email:
                reserver_email_address = user.email
            context = {
                'resource': self.resource.name,
                'begin': localize_datetime(self.begin),
                'end': localize_datetime(self.end),
                'begin_dt': self.begin,
                'end_dt': self.end,
                'time_range': self.format_time(),
                'reserver_name': reserver_name,
                'reserver_email_address': reserver_email_address,
                'require_assistance': self.require_assistance,
                'require_workstation': self.require_workstation,
                'extra_question': self.reservation_extra_questions,
                'home_municipality_id': reserver_home_municipality
            }
            directly_included_fields = ('number_of_participants', 'host_name',
                                        'event_subject', 'event_description',
                                        'reserver_phone_number',
                                        'billing_first_name',
                                        'billing_last_name',
                                        'billing_email_address',
                                        'billing_phone_number',
                                        'billing_address_street',
                                        'billing_address_zip',
                                        'billing_address_city')
            for field in directly_included_fields:
                context[field] = getattr(self, field)
            if self.resource.unit:
                context['unit'] = self.resource.unit.name
                context[
                    'unit_address'] = self.resource.unit.address_postal_full
                context['unit_id'] = self.resource.unit.id
                context[
                    'unit_map_service_id'] = self.resource.unit.map_service_id
            if self.can_view_access_code(user) and self.access_code:
                context['access_code'] = self.access_code

            if self.user and self.user.is_staff:
                context['staff_name'] = self.user.get_display_name()

            if notification_type in [
                    NotificationType.RESERVATION_CONFIRMED,
                    NotificationType.RESERVATION_CREATED
            ]:
                if self.resource.reservation_confirmed_notification_extra:
                    context[
                        'extra_content'] = self.resource.reservation_confirmed_notification_extra
            elif notification_type == NotificationType.RESERVATION_REQUESTED:
                if self.resource.reservation_requested_notification_extra:
                    context[
                        'extra_content'] = self.resource.reservation_requested_notification_extra

            # Get last main and ground plan images. Normally there shouldn't be more than one of each
            # of those images.
            images = self.resource.images.filter(
                type__in=('main', 'ground_plan')).order_by('-sort_order')
            main_image = next((i for i in images if i.type == 'main'), None)
            ground_plan_image = next(
                (i for i in images if i.type == 'ground_plan'), None)

            if main_image:
                main_image_url = main_image.get_full_url()
                if main_image_url:
                    context['resource_main_image_url'] = main_image_url
            if ground_plan_image:
                ground_plan_image_url = ground_plan_image.get_full_url()
                if ground_plan_image_url:
                    context[
                        'resource_ground_plan_image_url'] = ground_plan_image_url

            order = getattr(self, 'order', None)
            if order:
                context['order'] = order.get_notification_context(
                    language_code)

                all_products = []
                # Iterate through each order/product in order_lines.
                # Each order/product is appended to a list that is then set as the value of context['order'].
                for item in context["order"]["order_lines"]:
                    product = {}
                    product_fields = ('id', 'created_at', 'reservation_name',
                                      'name', 'quantity', 'price',
                                      'unit_price', 'unit_price_num',
                                      'tax_percentage', 'price_type',
                                      'price_period', 'order_number',
                                      'decimal_hours', 'pretax_price',
                                      'pretax_price_num', 'tax_price',
                                      'tax_price_num')
                    '''
                    product_values

                    These keys are used in the email template to display order/payment information.

                    id                  -   id of this order
                    created_at          -   creation date of the parent order
                    reservation_name    -   name of resource
                    name                -   name of this product
                    quantity            -   quantity of products, total price of product / single unit price of product
                    price               -   single unit price of this product
                    unit_price          -   total price of this product, string e.g. 75,00
                    unit_price_num      -   total price of this product, float e.g. 75.00
                    tax_percentage      -   tax percentage of this product
                    price_type          -   price type of product, per period / fixed
                    price_period        -   price period of product if type=per period, e.g. 00:30:00 for 30min 
                    order_number        -   id of parent order
                    pretax_price        -   price amount without tax, string e.g. 6,05 if total price is 7,5 with 24% vat
                    pretax_price_num    -   price amount without tax, float e.g. 6.05
                    tax_price           -   tax amount, string e.g. 1,45 if total price is 7,5 with 24% vat
                    tax_price_num       -   tax amount, float e.g. 1.45
                    '''
                    product_values = {
                        'id':
                        item["product"]["id"],
                        'created_at':
                        self.created_at.astimezone(
                            self.resource.unit.get_tz()).strftime(
                                '%d.%m.%Y %H:%M:%S'),
                        'reservation_name':
                        context["resource"],
                        'name':
                        item["product"]["name"],
                        'quantity':
                        float(item["unit_price"].replace(',', '.')) /
                        float(item["product"]["price"].replace(',', '.')),
                        'price':
                        item["product"]["price"],
                        'unit_price':
                        item["unit_price"],
                        'unit_price_num':
                        float(item["unit_price"].replace(',', '.')),
                        'tax_percentage':
                        item["product"]["tax_percentage"],
                        'price_type':
                        item["product"]["price_type"],
                        'price_period':
                        item["product"]["price_period"],
                        'order_number':
                        context["order"]["id"],
                        'pretax_price':
                        item["product"]["pretax_price"],
                        'pretax_price_num':
                        float(item["product"]["pretax_price"].replace(
                            ',', '.')),
                        'tax_price':
                        item["product"]["tax_price"],
                        'tax_price_num':
                        float(item["product"]["tax_price"].replace(',', '.'))
                    }

                    for field in product_fields:
                        if field == 'decimal_hours':
                            # price_period is None if price_type is 'fixed'
                            if item["product"]["price_period"] is not None:
                                # list of integers based on price_period string values, e.g. string '01:30:00' --> list [1,30,0]
                                price_unit_time = [
                                    int(x) for x in item["product"]
                                    ["price_period"].split(':')
                                ]
                                # calculate decimal time from list integers e.g. based on previous values, ((1*60) + 30) / 60 = 1.5
                                decimal_hours = ((price_unit_time[0] * 60) +
                                                 price_unit_time[1]) / 60
                                product[field] = decimal_hours
                            else:
                                # price_type is 'fixed'
                                product[field] = 1
                        else:
                            product[field] = product_values[field]

                    all_products.append(product)

                context['order_details'] = all_products

        if extra_context:
            context.update({'bulk_email_context': {**extra_context}})
        return context

    def send_reservation_mail(self,
                              notification_type,
                              user=None,
                              attachments=None,
                              action_by_official=False,
                              staff_email=None,
                              extra_context={},
                              is_reminder=False):
        # Check if resource's unit has a template group and if that group contains a notification template with correct notification type.
        if self.resource.unit.notification_template_group_id:
            try:
                # Check if template group contains a notification template with correct notification type.
                unit_template_group = NotificationTemplateGroup.objects.get(
                    id=self.resource.unit.notification_template_group_id)
                notification_template = unit_template_group.templates.get(
                    type=notification_type)

            except (NotificationTemplateGroup.DoesNotExist,
                    NotificationTemplate.DoesNotExist):
                # Otherwise search all notification templates for the default template of this type.
                try:
                    notification_template = NotificationTemplate.objects.get(
                        type=notification_type,
                        groups=None,
                        is_default_template=True)
                except NotificationTemplate.DoesNotExist:
                    return

            except NotificationTemplate.MultipleObjectsReturned:
                # Multiple templates found with same type, using default template instead.
                logger.error(
                    f"Template group: {unit_template_group.name} contains multiple templates of type: {notification_type}."
                )
                try:
                    notification_template = NotificationTemplate.objects.get(
                        type=notification_type,
                        groups=None,
                        is_default_template=True)
                except NotificationTemplate.DoesNotExist:
                    return

        # Otherwise search all notification templates for the default template of this type.
        else:
            try:
                notification_template = NotificationTemplate.objects.get(
                    type=notification_type,
                    groups=None,
                    is_default_template=True)
            except NotificationTemplate.DoesNotExist:
                return

        if self.resource.unit.sms_reminder:
            # only allow certain notification types as reminders e.g. exclude reservation_access_code_created
            allowed_reminder_notification_types = (
                NotificationType.RESERVATION_CONFIRMED,
                NotificationType.RESERVATION_CREATED,
                NotificationType.RESERVATION_CREATED_BY_OFFICIAL,
                NotificationType.RESERVATION_CREATED_WITH_ACCESS_CODE,
                NotificationType.
                RESERVATION_CREATED_WITH_ACCESS_CODE_BY_OFFICIAL,
            )

            if self.reminder and notification_type in allowed_reminder_notification_types:
                self.reminder.notification_type = self.reminder.notification_type if self.reminder.notification_type else notification_type
                self.reminder.user = self.reminder.user if self.reminder.user else user
                self.reminder.action_by_official = self.reminder.action_by_official if self.reminder.action_by_official else action_by_official
                self.reminder.save()
        """
        Stuff common to all reservation related mails.

        If user isn't given use self.user.
        """
        if getattr(self, 'order', None) and self.billing_email_address:
            email_address = self.billing_email_address
        elif user:
            email_address = user.email
        else:
            if not (self.reserver_email_address or self.user):
                return
            if action_by_official:
                email_address = self.reserver_email_address
            else:
                email_address = self.reserver_email_address or self.user.email
            user = self.user

        language = DEFAULT_LANG
        # use reservation's preferred_language if it exists
        if getattr(self, 'preferred_language', None):
            language = self.preferred_language
        # if user is defined and user.is_staff, use default lang
        if user and user.is_staff:
            language = DEFAULT_LANG

        context = self.get_notification_context(
            language,
            notification_type=notification_type,
            extra_context=extra_context)
        try:
            if staff_email:
                language = DEFAULT_LANG
            rendered_notification = notification_template.render(
                context, language)
        except NotificationTemplateException as e:
            print('NotifcationTemplateException: %s' % e)
            logger.error(e, exc_info=True, extra={'user': user.uuid})
            return

        if is_reminder:
            print("Sending SMS notification :: (%s) %s || LOCALE: %s" %
                  (self.reserver_phone_number,
                   rendered_notification['subject'], language))
            ret = send_respa_mail(
                email_address='%s@%s' % (self.reserver_phone_number,
                                         settings.GSM_NOTIFICATION_ADDRESS),
                subject=rendered_notification['subject'],
                body=rendered_notification['short_message'],
            )
            print(ret[1])
            return
        if staff_email:
            print("Sending automated mail :: (%s) %s || LOCALE: %s" %
                  (staff_email, rendered_notification['subject'], language))
            ret = send_respa_mail(staff_email,
                                  rendered_notification['subject'],
                                  rendered_notification['body'],
                                  rendered_notification['html_body'],
                                  attachments)
            print(ret[1])
        else:
            print("Sending automated mail :: (%s) %s || LOCALE: %s" %
                  (email_address, rendered_notification['subject'], language))
            ret = send_respa_mail(email_address,
                                  rendered_notification['subject'],
                                  rendered_notification['body'],
                                  rendered_notification['html_body'],
                                  attachments)
            print(ret[1])

    def notify_staff_about_reservation(self, notification):
        if self.resource.resource_staff_emails:
            for email in self.resource.resource_staff_emails:
                self.send_reservation_mail(notification, staff_email=email)
        else:
            notify_users = self.resource.get_users_with_perm(
                'can_approve_reservation')
            if len(notify_users) > 100:
                raise Exception("Refusing to notify more than 100 users (%s)" %
                                self)
            for user in notify_users:
                self.send_reservation_mail(notification, user=user)

    def send_reservation_requested_mail(self, action_by_official=False):
        notification = NotificationType.RESERVATION_REQUESTED_BY_OFFICIAL if action_by_official else NotificationType.RESERVATION_REQUESTED
        self.send_reservation_mail(notification)

    def send_reservation_modified_mail(self, action_by_official=False):
        notification = NotificationType.RESERVATION_MODIFIED_BY_OFFICIAL if action_by_official else NotificationType.RESERVATION_MODIFIED
        self.send_reservation_mail(notification,
                                   action_by_official=action_by_official)

    def send_reservation_denied_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_DENIED)

    def send_reservation_confirmed_mail(self):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        attachment = ('reservation.ics', ical_file, 'text/calendar')
        self.send_reservation_mail(NotificationType.RESERVATION_CONFIRMED,
                                   attachments=[attachment])

    def send_reservation_cancelled_mail(self, action_by_official=False):
        notification = NotificationType.RESERVATION_CANCELLED_BY_OFFICIAL if action_by_official else NotificationType.RESERVATION_CANCELLED
        self.send_reservation_mail(notification,
                                   action_by_official=action_by_official)

    def send_reservation_created_mail(self, action_by_official=False):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        attachment = 'reservation.ics', ical_file, 'text/calendar'
        notification = NotificationType.RESERVATION_CREATED_BY_OFFICIAL if action_by_official else NotificationType.RESERVATION_CREATED
        self.send_reservation_mail(notification,
                                   attachments=[attachment],
                                   action_by_official=action_by_official)

    def send_reservation_created_with_access_code_mail(self,
                                                       action_by_official=False
                                                       ):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        attachment = 'reservation.ics', ical_file, 'text/calendar'
        notification = NotificationType.RESERVATION_CREATED_WITH_ACCESS_CODE_OFFICIAL_BY_OFFICIAL if action_by_official else NotificationType.RESERVATION_CREATED_WITH_ACCESS_CODE
        self.send_reservation_mail(notification,
                                   attachments=[attachment],
                                   action_by_official=action_by_official)

    def send_access_code_created_mail(self):
        self.send_reservation_mail(
            NotificationType.RESERVATION_ACCESS_CODE_CREATED)

    def save(self, *args, **kwargs):
        self.duration = DateTimeTZRange(self.begin, self.end, '[)')

        if not self.access_code:
            access_code_type = self.resource.access_code_type
            if self.resource.is_access_code_enabled(
            ) and self.resource.generate_access_codes:
                self.access_code = generate_access_code(access_code_type)

        return super().save(*args, **kwargs)
Exemplo n.º 25
0
class Layer(BaseDate):
    """
    Layer Model
    A layer represent a categorization of nodes.
    Layers might have geographical boundaries and might be managed by certain organizations.
    """
    name = models.CharField(_('name'), max_length=50, unique=True)
    slug = models.SlugField(max_length=50, db_index=True, unique=True)
    description = models.CharField(
        _('description'),
        max_length=250,
        blank=True,
        null=True,
        help_text=_('short description of this layer'))
    text = models.TextField(
        _('extended text'),
        blank=True,
        null=True,
        help_text=_(
            'extended description, specific instructions, links, ecc.'))
    # record management
    is_published = models.BooleanField(_('published'), default=True)
    is_external = models.BooleanField(_('is it external?'), default=False)
    # geographic related fields
    area = models.GeometryField(
        _('area'),
        help_text=
        _('If a polygon is used nodes of this layer will have to be contained in it.\
                                                        If a point is used nodes of this layer can be located anywhere. Lines are not allowed.'
          ))
    # organizational
    organization = models.CharField(
        _('organization'),
        max_length=255,
        blank=True,
        help_text=_('Organization which is responsible to manage this layer'))
    website = models.URLField(_('Website'), blank=True, null=True)
    email = models.EmailField(
        _('email'),
        blank=True,
        help_text=
        _("""possibly an email address that delivers messages to all the active participants;
                                          if you don't have such an email you can add specific users in the "mantainers" field"""
          ))
    mantainers = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        verbose_name=_('mantainers'),
        blank=True,
        help_text=
        _('you can specify the users who are mantaining this layer so they will receive emails from the system'
          ))
    # settings
    nodes_minimum_distance = models.IntegerField(
        default=NODES_MINIMUM_DISTANCE,
        help_text=
        _('minimum distance between nodes in meters, 0 means there is no minimum distance'
          ))
    new_nodes_allowed = models.BooleanField(
        _('new nodes allowed'),
        default=True,
        help_text=_('indicates whether users can add new nodes to this layer'))
    data = DictionaryField(_('extra data'),
                           schema=HSTORE_SCHEMA,
                           null=True,
                           editable=False)

    # default manager
    objects = LayerManager()

    # this is needed to check if the is_published is changing
    # explained here:
    # http://stackoverflow.com/questions/1355150/django-when-saving-how-can-you-check-if-a-field-has-changed
    _current_is_published = None

    class Meta:
        db_table = 'layers_layer'
        app_label = 'layers'

    def __unicode__(self):
        return self.name

    def __init__(self, *args, **kwargs):
        """ Fill _current_is_published """
        super(Layer, self).__init__(*args, **kwargs)
        # set current is_published, but only if it is an existing layer
        if self.pk:
            self._current_is_published = self.is_published

    def save(self, *args, **kwargs):
        """
        intercepts changes to is_published and fires layer_is_published_changed signal
        """
        super(Layer, self).save(*args, **kwargs)

        # if is_published of an existing layer changes
        if self.pk and self.is_published != self._current_is_published:
            # send django signal
            layer_is_published_changed.send(
                sender=self.__class__,
                instance=self,
                old_is_published=self._current_is_published,
                new_is_published=self.is_published)
            # unpublish nodes
            self.update_nodes_published()

        # update _current_is_published
        self._current_is_published = self.is_published

    def clean(self):
        """
        Ensure area is either a Point or a Polygon
        """
        if not isinstance(self.area, (Polygon, Point)):
            raise ValidationError('area can be only of type Polygon or Point')

    @property
    def center(self):
        # if area is point just return that
        if isinstance(self.area, Point) or self.area is None:
            return self.area
        # otherwise return point_on_surface or centroid
        try:
            # point_on_surface guarantees that the point is within the geometry
            return self.area.point_on_surface
        except GEOSException:
            # fall back on centroid which may not be within the geometry
            # for example, a horseshoe shaped polygon
            return self.area.centroid

    def update_nodes_published(self):
        """ publish or unpublish nodes of current layer """
        if self.pk:
            self.node_set.all().update(is_published=self.is_published)

    if 'grappelli' in settings.INSTALLED_APPS:

        @staticmethod
        def autocomplete_search_fields():
            return ('name__icontains', 'slug__icontains')
Exemplo n.º 26
0
class Reporter(CreatedUpdatedModel):
    """
    Privacy sensitive information on reporter.

    This information will be anonymized after X time
    """
    _signal = models.ForeignKey('signals.Signal',
                                related_name='reporters',
                                null=False,
                                on_delete=models.CASCADE)

    email = models.EmailField(blank=True, null=True)
    phone = models.CharField(max_length=17, blank=True, null=True)

    email_anonymized = models.BooleanField(default=False)
    phone_anonymized = models.BooleanField(default=False)

    sharing_allowed = models.BooleanField(default=False)

    class Meta:
        permissions = (('sia_can_view_contact_details',
                        'Inzien van contactgegevens melder (in melding)'), )

    @property
    def is_anonymized(self):
        """
        Checks if an anonymous reporter is anonymized?
        """
        return self.is_anonymous and (self.email_anonymized
                                      or self.phone_anonymized)

    @property
    def is_anonymous(self):
        """
        Checks if a reporter is anonymous
        """
        return not self.email and not self.phone

    def anonymize(self, always_call_save=False):
        call_save = False
        if not self.email_anonymized and self.email:
            self.email_anonymized = True
            call_save = True

        if not self.phone_anonymized and self.phone:
            self.phone_anonymized = True
            call_save = True

        if call_save or always_call_save:
            self.save()

    def save(self, *args, **kwargs):
        """
        Make sure that the email and phone are set to none while saving the Reporter
        """
        if self.email_anonymized:
            self.email = None

        if self.phone_anonymized:
            self.phone = None

        super().save(*args, **kwargs)
Exemplo n.º 27
0
class Place(MPTTModel, BaseModel, SchemalessFieldMixin, ImageMixin,
            ReplacedByMixin):
    objects = BaseTreeQuerySet.as_manager()
    geo_objects = objects

    publisher = models.ForeignKey('django_orghierarchy.Organization',
                                  on_delete=models.CASCADE,
                                  verbose_name=_('Publisher'),
                                  db_index=True)
    info_url = models.URLField(verbose_name=_('Place home page'),
                               null=True,
                               blank=True,
                               max_length=1000)
    description = models.TextField(verbose_name=_('Description'),
                                   null=True,
                                   blank=True)
    parent = TreeForeignKey('self',
                            on_delete=models.CASCADE,
                            null=True,
                            blank=True,
                            related_name='children')

    position = models.PointField(srid=settings.PROJECTION_SRID,
                                 null=True,
                                 blank=True)

    email = models.EmailField(verbose_name=_('E-mail'), null=True, blank=True)
    telephone = models.CharField(verbose_name=_('Telephone'),
                                 max_length=128,
                                 null=True,
                                 blank=True)
    contact_type = models.CharField(verbose_name=_('Contact type'),
                                    max_length=255,
                                    null=True,
                                    blank=True)
    street_address = models.CharField(verbose_name=_('Street address'),
                                      max_length=255,
                                      null=True,
                                      blank=True)
    address_locality = models.CharField(verbose_name=_('Address locality'),
                                        max_length=255,
                                        null=True,
                                        blank=True)
    address_region = models.CharField(verbose_name=_('Address region'),
                                      max_length=255,
                                      null=True,
                                      blank=True)
    postal_code = models.CharField(verbose_name=_('Postal code'),
                                   max_length=128,
                                   null=True,
                                   blank=True)
    post_office_box_num = models.CharField(verbose_name=_('PO BOX'),
                                           max_length=128,
                                           null=True,
                                           blank=True)
    address_country = models.CharField(verbose_name=_('Country'),
                                       max_length=2,
                                       null=True,
                                       blank=True)

    deleted = models.BooleanField(verbose_name=_('Deleted'), default=False)
    replaced_by = models.ForeignKey('Place',
                                    on_delete=models.SET_NULL,
                                    related_name='aliases',
                                    null=True,
                                    blank=True)
    divisions = models.ManyToManyField(AdministrativeDivision,
                                       verbose_name=_('Divisions'),
                                       related_name='places',
                                       blank=True)
    n_events = models.IntegerField(
        verbose_name=_('event count'),
        help_text=_('number of events in this location'),
        default=0,
        editable=False,
        db_index=True)
    n_events_changed = models.BooleanField(default=False, db_index=True)

    class Meta:
        verbose_name = _('place')
        verbose_name_plural = _('places')
        unique_together = (('data_source', 'origin_id'), )

    def __unicode__(self):
        values = filter(
            lambda x: x,
            [self.street_address, self.postal_code, self.address_locality])
        return u', '.join(values)

    @transaction.atomic
    def save(self, *args, **kwargs):
        if self._has_circular_replacement():
            raise Exception(
                "Trying to replace this place with a place that is replaced by this place"
                "Please refrain from creating circular replacements and"
                "remove one of the replacements."
                "We don't want homeless events.")

        if self.replaced_by and not self.deleted:
            self.deleted = True
            logger.warning(
                "Place replaced without soft deleting. Soft deleting automatically",
                extra={'place': self})

        # needed to remap events to replaced location
        old_replaced_by = None
        if self.id:
            try:
                old_replaced_by = Place.objects.get(id=self.id).replaced_by
            except Place.DoesNotExist:
                pass

        super().save(*args, **kwargs)

        # needed to remap events to replaced location
        if not old_replaced_by == self.replaced_by:
            Event.objects.filter(location=self).update(
                location=self.replaced_by)
            # Update doesn't call save so we update event numbers manually.
            # Not all of the below are necessarily present.
            ids_to_update = [
                event.id for event in (self, self.replaced_by, old_replaced_by)
                if event
            ]
            Place.objects.filter(id__in=ids_to_update).update(
                n_events_changed=True)

        if self.position:
            self.divisions.set(
                AdministrativeDivision.objects.filter(
                    type__type__in=('district', 'sub_district', 'neighborhood',
                                    'muni'),
                    geometry__boundary__contains=self.position))
        else:
            self.divisions.clear()
Exemplo n.º 28
0
class Campus(models.Model):
    provider = models.ForeignKey(
        'ford3.provider',
        on_delete=models.CASCADE)
    name = models.CharField(
        blank=False,
        null=False,
        unique=False,
        help_text='The name of the campus',
        max_length=255)
    location = models.PointField(
        blank=True,
        null=True,
        help_text='The spatial point position of the campus')
    photo = models.FileField(
        blank=False,
        null=True,
        help_text='Representative photo of campus',
        upload_to='campus/photo'
    )
    telephone = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' switchboard",
        max_length=16)
    email = models.EmailField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' email",
        max_length=255)
    max_students_per_year = models.PositiveIntegerField(
        blank=False,
        null=True,
        unique=False,
        help_text="Maximum number of students")
    physical_address_line_1 = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' physical address details",
        max_length=255)
    physical_address_line_2 = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' physical address details",
        max_length=255)

    physical_address_city = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' physical address city",
        max_length=255)

    physical_address_postal_code = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' physical address post code",
        max_length=255)

    postal_address_differs = models.BooleanField(
        blank=False,
        null=True,
        default=False,
        help_text="Is the postal address different from the physical address?")

    postal_address_line_1 = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' postal address",
        max_length=255)

    postal_address_line_2 = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' postal address",
        max_length=255)

    postal_address_city = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' postal address city",
        max_length=255)

    postal_address_postal_code = models.CharField(
        blank=False,
        null=True,
        unique=False,
        help_text="The campus' postal adress code",
        max_length=255)

    created_at = models.DateTimeField(
        auto_now_add=True)
    edited_at = models.DateTimeField(
        auto_now=True)

    created_by = models.ForeignKey(
        'ford3.User',
        null=True,
        on_delete=models.PROTECT,
        related_name='campus_created_by'
    )

    edited_by = models.ForeignKey(
        'ford3.User',
        null=True,
        on_delete=models.PROTECT,
        related_name='campus_edited_by'
    )

    deleted_by = models.ForeignKey(
        'ford3.User',
        null=True,
        on_delete=models.PROTECT,
        related_name='campus_deleted_by'
    )

    deleted = models.BooleanField(
        default=False,
        help_text="Campus has been deleted")

    completion_rate = models.PositiveIntegerField(
        blank=True,
        null=True,
        default=0,
        help_text="How much of the campus' details has been completed?"
    )

    COMPLETION_RULES = completion_rules

    objects = models.Manager()
    active_objects = ActiveCampusManager()

    def save(self, *args, **kwargs):
        if self.id is None:
            if len(self.name) == 0:
                raise ValidationError({'campus': 'Name is required.'})

            if Campus.objects.filter(
                provider_id=self.provider.id,
                    name__iexact=self.name,
                    deleted=False).exists():
                raise ValidationError({'campus': 'Name is already taken.'})
        super().save(*args, **kwargs)

    def save_location_data(self, cleaned_data):
        x_value = cleaned_data['location_value_x']
        y_value = cleaned_data['location_value_y']
        geometry_point = GEOSGeometry(Point(
            x_value,
            y_value))
        self.location = geometry_point

        self.save()

    def soft_delete(self):
        self.soft_delete_all_qualifications()
        self.deleted = True
        self.save()

    @property
    def events(self):
        event_query = CampusEvent.active_objects.filter(
            campus__id=self.id).order_by('id').values(
                'id',
                'name',
                'date_start',
                'date_end',
                'http_link')
        return list(event_query)

    @property
    def qualifications(self):
        queryset = self.qualification_set \
            .filter(deleted=False) \
            .values(
                'id',
                'saqa_qualification__id',
                'saqa_qualification__name',
                'saqa_qualification__saqa_id',
                'saqa_qualification__accredited',
                'edited_at',
                'published',
                'ready_to_publish',
                'completion_rate') \
            .order_by('id')
        return list(queryset)

    @property
    def saqa_ids(self):
        return [
            str(s['saqa_qualification__id'])
            for s in self.qualifications]

    @property
    def physical_address(self):
        if self.physical_address_line_1 is None \
            and self.physical_address_line_2 is None \
                and self.physical_address_city is None \
                and self.physical_address_postal_code is None:
            return None

        return f'''
            {self.physical_address_line_1}
            {self.physical_address_line_2}
            {self.physical_address_city}
            {self.physical_address_postal_code}
        '''

    @property
    def postal_address(self):
        if self.postal_address_line_1 is None \
            and self.postal_address_line_2 is None \
                and self.postal_address_city is None \
                and self.postal_address_postal_code is None:
            return None

        return f'''
            {self.postal_address_line_1}
            {self.postal_address_line_2}
            {self.postal_address_city}
            {self.postal_address_postal_code}
        '''

    @property
    def qualifications_completion_rate(self):
        try:
            return int(sum([
                qualification['completion_rate']
                for qualification in self.qualifications
            ]) / len(self.qualifications))
        except ZeroDivisionError:
            return 0

    def save_postal_data(self, form_data):
        postal_address_differs = form_data.get(
            'postal_address_differs', '')
        physical_address_line_1 = form_data.get(
                'physical_address_line_1', '')
        physical_address_line_2 = form_data.get(
            'physical_address_line_2', '')
        physical_address_city = form_data.get(
            'physical_address_city', '')
        physical_address_postal_code = form_data.get(
            'physical_address_postal_code', '')
        if not postal_address_differs:
            postal_address_line_1 = physical_address_line_1
            postal_address_line_2 = physical_address_line_2
            postal_address_city = physical_address_city
            postal_address_postal_code = physical_address_postal_code
        else:
            postal_address_line_1 = form_data.get(
                'postal_address_line_1', '')
            postal_address_line_2 = form_data.get(
                'postal_address_line_2', '')
            postal_address_city = form_data.get(
                'postal_address_city', '')
            postal_address_postal_code = form_data.get(
                'postal_address_postal_code', '')
        setattr(self,
                'physical_address_line_1', physical_address_line_1)
        setattr(self,
                'physical_address_line_2', physical_address_line_2)
        setattr(self,
                'physical_address_city', physical_address_city)
        setattr(self,
                'physical_address_postal_code', physical_address_postal_code)
        setattr(self,
                'postal_address_line_1', postal_address_line_1)
        setattr(self,
                'postal_address_line_2', postal_address_line_2)
        setattr(self,
                'postal_address_city', postal_address_city)
        setattr(self,
                'postal_address_postal_code', postal_address_postal_code)
        setattr(self,
                'postal_address_differs', postal_address_differs)
        self.save()

    def save_form_data(self, form_data):
        for key, value in form_data.items():
            setattr(self, key, value)
        self.save()

    def save_qualifications(self, form_data, created_by):
        if len(form_data['saqa_ids']) == 0:
            return

        ids = [
            sid for sid in form_data['saqa_ids'].split(' ')
            if sid not in self.saqa_ids
        ]

        for saqa_id in ids:
            qualif = self.qualification_set.create(
                created_by=created_by,
                edited_by=created_by
            )
            qualif.set_saqa_qualification(saqa_id)
            qualif.save()

    def delete_qualifications(self, form_data):
        # ids missing in form_data must be deleted
        ids = [
            sid for sid in self.saqa_ids
            if sid not in form_data['saqa_ids'].split(' ')
        ]

        for saqa_id in ids:
            qualif = self.qualification_set.filter(
                saqa_qualification__id=saqa_id,
                campus=self)
            qualif.update(deleted=True)

    def soft_delete_all_qualifications(self):
        for qualification in self.qualification_set.all():
            qualification.soft_delete()

    def __str__(self):
        return self.name
Exemplo n.º 29
0
class Reservation(ModifiableModel):
    CREATED = 'created'
    CANCELLED = 'cancelled'
    CONFIRMED = 'confirmed'
    DENIED = 'denied'
    REQUESTED = 'requested'
    WAITING_FOR_PAYMENT = 'waiting_for_payment'
    STATE_CHOICES = (
        (CREATED, _('created')),
        (CANCELLED, _('cancelled')),
        (CONFIRMED, _('confirmed')),
        (DENIED, _('denied')),
        (REQUESTED, _('requested')),
        (WAITING_FOR_PAYMENT, _('waiting for payment')),
    )

    TYPE_NORMAL = 'normal'
    TYPE_BLOCKED = 'blocked'
    TYPE_CHOICES = (
        (TYPE_NORMAL, _('Normal reservation')),
        (TYPE_BLOCKED, _('Resource blocked')),
    )

    resource = models.ForeignKey('Resource',
                                 verbose_name=_('Resource'),
                                 db_index=True,
                                 related_name='reservations',
                                 on_delete=models.PROTECT)
    begin = models.DateTimeField(verbose_name=_('Begin time'))
    end = models.DateTimeField(verbose_name=_('End time'))
    duration = pgfields.DateTimeRangeField(
        verbose_name=_('Length of reservation'),
        null=True,
        blank=True,
        db_index=True)
    comments = models.TextField(null=True,
                                blank=True,
                                verbose_name=_('Comments'))
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             verbose_name=_('User'),
                             null=True,
                             blank=True,
                             db_index=True,
                             on_delete=models.PROTECT)
    state = models.CharField(max_length=32,
                             choices=STATE_CHOICES,
                             verbose_name=_('State'),
                             default=CREATED)
    approver = models.ForeignKey(settings.AUTH_USER_MODEL,
                                 verbose_name=_('Approver'),
                                 related_name='approved_reservations',
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL)
    staff_event = models.BooleanField(verbose_name=_('Is staff event'),
                                      default=False)
    type = models.CharField(blank=False,
                            verbose_name=_('Type'),
                            max_length=32,
                            choices=TYPE_CHOICES,
                            default=TYPE_NORMAL)

    # access-related fields
    access_code = models.CharField(verbose_name=_('Access code'),
                                   max_length=32,
                                   null=True,
                                   blank=True)

    # EXTRA FIELDS START HERE

    event_subject = models.CharField(max_length=200,
                                     verbose_name=_('Event subject'),
                                     blank=True)
    event_description = models.TextField(verbose_name=_('Event description'),
                                         blank=True)
    number_of_participants = models.PositiveSmallIntegerField(
        verbose_name=_('Number of participants'), blank=True, null=True)
    participants = models.TextField(verbose_name=_('Participants'), blank=True)
    host_name = models.CharField(verbose_name=_('Host name'),
                                 max_length=100,
                                 blank=True)
    reservation_extra_questions = models.TextField(
        verbose_name=_('Reservation extra questions'), blank=True)

    reserver_name = models.CharField(verbose_name=_('Reserver name'),
                                     max_length=100,
                                     blank=True)
    reserver_id = models.CharField(
        verbose_name=_('Reserver ID (business or person)'),
        max_length=30,
        blank=True)
    reserver_email_address = models.EmailField(
        verbose_name=_('Reserver email address'), blank=True)
    reserver_phone_number = models.CharField(
        verbose_name=_('Reserver phone number'), max_length=30, blank=True)
    reserver_address_street = models.CharField(
        verbose_name=_('Reserver address street'), max_length=100, blank=True)
    reserver_address_zip = models.CharField(
        verbose_name=_('Reserver address zip'), max_length=30, blank=True)
    reserver_address_city = models.CharField(
        verbose_name=_('Reserver address city'), max_length=100, blank=True)
    company = models.CharField(verbose_name=_('Company'),
                               max_length=100,
                               blank=True)
    billing_first_name = models.CharField(verbose_name=_('Billing first name'),
                                          max_length=100,
                                          blank=True)
    billing_last_name = models.CharField(verbose_name=_('Billing last name'),
                                         max_length=100,
                                         blank=True)
    billing_email_address = models.EmailField(
        verbose_name=_('Billing email address'), blank=True)
    billing_phone_number = models.CharField(
        verbose_name=_('Billing phone number'), max_length=30, blank=True)
    billing_address_street = models.CharField(
        verbose_name=_('Billing address street'), max_length=100, blank=True)
    billing_address_zip = models.CharField(
        verbose_name=_('Billing address zip'), max_length=30, blank=True)
    billing_address_city = models.CharField(
        verbose_name=_('Billing address city'), max_length=100, blank=True)

    # If the reservation was imported from another system, you can store the original ID in the field below.
    origin_id = models.CharField(verbose_name=_('Original ID'),
                                 max_length=50,
                                 editable=False,
                                 null=True)

    objects = ReservationQuerySet.as_manager()

    class Meta:
        verbose_name = _("reservation")
        verbose_name_plural = _("reservations")
        ordering = ('id', )

    def _save_dt(self, attr, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        save_dt(self, attr, dt, self.resource.unit.time_zone)

    def _get_dt(self, attr, tz):
        return get_dt(self, attr, tz)

    @property
    def begin_tz(self):
        return self.begin

    @begin_tz.setter
    def begin_tz(self, dt):
        self._save_dt('begin', dt)

    def get_begin_tz(self, tz):
        return self._get_dt("begin", tz)

    @property
    def end_tz(self):
        return self.end

    @end_tz.setter
    def end_tz(self, dt):
        """
        Any DateTime object is converted to UTC time zone aware DateTime
        before save

        If there is no time zone on the object, resource's time zone will
        be assumed through its unit's time zone
        """
        self._save_dt('end', dt)

    def get_end_tz(self, tz):
        return self._get_dt("end", tz)

    def is_active(self):
        return self.end >= timezone.now() and self.state not in (
            Reservation.CANCELLED, Reservation.DENIED)

    def is_own(self, user):
        if not (user and user.is_authenticated):
            return False
        return user == self.user

    def need_manual_confirmation(self):
        return self.resource.need_manual_confirmation

    def are_extra_fields_visible(self, user):
        # the following logic is used also implemented in ReservationQuerySet
        # so if this is changed that probably needs to be changed as well

        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_access_code(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_access_code(user)

    def set_state(self, new_state, user):
        # Make sure it is a known state
        assert new_state in (Reservation.REQUESTED, Reservation.CONFIRMED,
                             Reservation.DENIED, Reservation.CANCELLED,
                             Reservation.WAITING_FOR_PAYMENT)

        old_state = self.state
        if new_state == old_state:
            if old_state == Reservation.CONFIRMED:
                reservation_modified.send(sender=self.__class__,
                                          instance=self,
                                          user=user)
            return

        if new_state == Reservation.CONFIRMED:
            self.approver = user
            reservation_confirmed.send(sender=self.__class__,
                                       instance=self,
                                       user=user)
        elif old_state == Reservation.CONFIRMED:
            self.approver = None

        user_is_staff = self.user is not None and self.user.is_staff

        # Notifications
        if new_state == Reservation.REQUESTED:
            self.send_reservation_requested_mail()
            self.send_reservation_requested_mail_to_officials()
        elif new_state == Reservation.CONFIRMED:
            if self.need_manual_confirmation():
                self.send_reservation_confirmed_mail()
            elif self.access_code:
                self.send_reservation_created_with_access_code_mail()
            else:
                if not user_is_staff:
                    # notifications are not sent from staff created reservations to avoid spam
                    self.send_reservation_created_mail()
        elif new_state == Reservation.DENIED:
            self.send_reservation_denied_mail()
        elif new_state == Reservation.CANCELLED:
            order = self.get_order()
            if order:
                if order.state == order.CANCELLED:
                    self.send_reservation_cancelled_mail()
            else:
                if user != self.user:
                    self.send_reservation_cancelled_mail()
            reservation_cancelled.send(sender=self.__class__,
                                       instance=self,
                                       user=user)

        self.state = new_state
        self.save()

    def can_modify(self, user):
        if not user:
            return False

        if self.state == Reservation.WAITING_FOR_PAYMENT:
            return False

        if self.get_order():
            return self.resource.can_modify_paid_reservations(user)

        # reservations that need manual confirmation and are confirmed cannot be
        # modified or cancelled without reservation approve permission
        cannot_approve = not self.resource.can_approve_reservations(user)
        if self.need_manual_confirmation(
        ) and self.state == Reservation.CONFIRMED and cannot_approve:
            return False

        return self.user == user or self.resource.can_modify_reservations(user)

    def can_add_comment(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_access_reservation_comments(user)

    def can_view_field(self, user, field):
        if field not in RESERVATION_EXTRA_FIELDS:
            return True
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_extra_fields(user)

    def can_view_catering_orders(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_catering_orders(user)

    def can_add_product_order(self, user):
        return self.is_own(user)

    def can_view_product_orders(self, user):
        if self.is_own(user):
            return True
        return self.resource.can_view_reservation_product_orders(user)

    def get_order(self):
        return getattr(self, 'order', None)

    def format_time(self):
        tz = self.resource.unit.get_tz()
        begin = self.begin.astimezone(tz)
        end = self.end.astimezone(tz)
        return format_dt_range(translation.get_language(), begin, end)

    def __str__(self):
        if self.state != Reservation.CONFIRMED:
            state_str = ' (%s)' % self.state
        else:
            state_str = ''
        return "%s: %s%s" % (self.format_time(), self.resource, state_str)

    def clean(self, **kwargs):
        """
        Check restrictions that are common to all reservations.

        If this reservation isn't yet saved and it will modify an existing reservation,
        the original reservation need to be provided in kwargs as 'original_reservation', so
        that it can be excluded when checking if the resource is available.
        """

        if 'user' in kwargs:
            user = kwargs['user']
        else:
            user = self.user

        user_is_admin = user and self.resource.is_admin(user)

        if self.end <= self.begin:
            raise ValidationError(
                _("You must end the reservation after it has begun"))

        # Check that begin and end times are on valid time slots.
        opening_hours = self.resource.get_opening_hours(
            self.begin.date(), self.end.date())
        for dt in (self.begin, self.end):
            days = opening_hours.get(dt.date(), [])
            day = next((day for day in days if day['opens'] is not None
                        and day['opens'] <= dt <= day['closes']), None)
            if day and not is_valid_time_slot(dt, self.resource.slot_size,
                                              day['opens']):
                raise ValidationError(
                    _("Begin and end time must match time slots"),
                    code='invalid_time_slot')

        # Check if Unit has disallow_overlapping_reservations value of True
        if (self.resource.unit.disallow_overlapping_reservations and
                not self.resource.can_create_overlapping_reservations(user)):
            reservations_for_same_unit = Reservation.objects.filter(
                user=user, resource__unit=self.resource.unit)
            valid_reservations_for_same_unit = reservations_for_same_unit.exclude(
                state=Reservation.CANCELLED)
            user_has_conflicting_reservations = valid_reservations_for_same_unit.filter(
                Q(begin__gt=self.begin, begin__lt=self.end)
                | Q(begin__lt=self.begin, end__gt=self.begin)
                | Q(begin__gte=self.begin, end__lte=self.end))

            if user_has_conflicting_reservations:
                raise ValidationError(_(
                    'This unit does not allow overlapping reservations for its resources'
                ),
                                      code='conflicting_reservation')

        original_reservation = self if self.pk else kwargs.get(
            'original_reservation', None)
        if self.resource.check_reservation_collision(self.begin, self.end,
                                                     original_reservation):
            raise ValidationError(
                _("The resource is already reserved for some of the period"))

        if not user_is_admin:
            if (self.end - self.begin) < self.resource.min_period:
                raise ValidationError(
                    _("The minimum reservation length is %(min_period)s") % {
                        'min_period': humanize_duration(
                            self.resource.min_period)
                    })
        else:
            if not (self.end - self.begin
                    ) % self.resource.slot_size == datetime.timedelta(0):
                raise ValidationError(
                    _("The minimum reservation length is %(slot_size)s") %
                    {'slot_size': humanize_duration(self.resource.slot_size)})

        if self.access_code:
            validate_access_code(self.access_code,
                                 self.resource.access_code_type)

    def get_notification_context(self,
                                 language_code,
                                 user=None,
                                 notification_type=None):
        if not user:
            user = self.user
        with translation.override(language_code):
            reserver_name = self.reserver_name
            reserver_email_address = self.reserver_email_address
            if not reserver_name and self.user and self.user.get_display_name(
            ):
                reserver_name = self.user.get_display_name()
            if not reserver_email_address and user and user.email:
                reserver_email_address = user.email
            context = {
                'resource': self.resource.name,
                'begin': localize_datetime(self.begin),
                'end': localize_datetime(self.end),
                'begin_dt': self.begin,
                'end_dt': self.end,
                'time_range': self.format_time(),
                'reserver_name': reserver_name,
                'reserver_email_address': reserver_email_address,
            }
            directly_included_fields = (
                'number_of_participants',
                'host_name',
                'event_subject',
                'event_description',
                'reserver_phone_number',
                'billing_first_name',
                'billing_last_name',
                'billing_email_address',
                'billing_phone_number',
                'billing_address_street',
                'billing_address_zip',
                'billing_address_city',
            )
            for field in directly_included_fields:
                context[field] = getattr(self, field)
            if self.resource.unit:
                context['unit'] = self.resource.unit.name
                context['unit_id'] = self.resource.unit.id
            if self.can_view_access_code(user) and self.access_code:
                context['access_code'] = self.access_code

            if notification_type == NotificationType.RESERVATION_CONFIRMED:
                if self.resource.reservation_confirmed_notification_extra:
                    context[
                        'extra_content'] = self.resource.reservation_confirmed_notification_extra
            elif notification_type == NotificationType.RESERVATION_REQUESTED:
                if self.resource.reservation_requested_notification_extra:
                    context[
                        'extra_content'] = self.resource.reservation_requested_notification_extra

            # Get last main and ground plan images. Normally there shouldn't be more than one of each
            # of those images.
            images = self.resource.images.filter(
                type__in=('main', 'ground_plan')).order_by('-sort_order')
            main_image = next((i for i in images if i.type == 'main'), None)
            ground_plan_image = next(
                (i for i in images if i.type == 'ground_plan'), None)

            if main_image:
                main_image_url = main_image.get_full_url()
                if main_image_url:
                    context['resource_main_image_url'] = main_image_url
            if ground_plan_image:
                ground_plan_image_url = ground_plan_image.get_full_url()
                if ground_plan_image_url:
                    context[
                        'resource_ground_plan_image_url'] = ground_plan_image_url

            order = getattr(self, 'order', None)
            if order:
                context['order'] = order.get_notification_context(
                    language_code)

        return context

    def send_reservation_mail(self,
                              notification_type,
                              user=None,
                              attachments=None):
        """
        Stuff common to all reservation related mails.

        If user isn't given use self.user.
        """
        try:
            notification_template = NotificationTemplate.objects.get(
                type=notification_type)
        except NotificationTemplate.DoesNotExist:
            return

        if user:
            email_address = user.email
        else:
            if not (self.reserver_email_address or self.user):
                return
            email_address = self.reserver_email_address or self.user.email
            user = self.user

        language = user.get_preferred_language() if user else DEFAULT_LANG
        context = self.get_notification_context(
            language, notification_type=notification_type)

        try:
            rendered_notification = notification_template.render(
                context, language)
        except NotificationTemplateException as e:
            logger.error(e, exc_info=True, extra={'user': user.uuid})
            return

        send_respa_mail(email_address, rendered_notification['subject'],
                        rendered_notification['body'],
                        rendered_notification['html_body'], attachments)

    def send_reservation_requested_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_REQUESTED)

    def send_reservation_requested_mail_to_officials(self):
        notify_users = self.resource.get_users_with_perm(
            'can_approve_reservation')
        if len(notify_users) > 100:
            raise Exception("Refusing to notify more than 100 users (%s)" %
                            self)
        for user in notify_users:
            self.send_reservation_mail(
                NotificationType.RESERVATION_REQUESTED_OFFICIAL, user=user)

    def send_reservation_denied_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_DENIED)

    def send_reservation_confirmed_mail(self):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        attachment = ('reservation.ics', ical_file, 'text/calendar')
        self.send_reservation_mail(NotificationType.RESERVATION_CONFIRMED,
                                   attachments=[attachment])

    def send_reservation_cancelled_mail(self):
        self.send_reservation_mail(NotificationType.RESERVATION_CANCELLED)

    def send_reservation_created_mail(self):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        attachment = 'reservation.ics', ical_file, 'text/calendar'
        self.send_reservation_mail(NotificationType.RESERVATION_CREATED,
                                   attachments=[attachment])

    def send_reservation_created_with_access_code_mail(self):
        reservations = [self]
        ical_file = build_reservations_ical_file(reservations)
        attachment = 'reservation.ics', ical_file, 'text/calendar'
        self.send_reservation_mail(
            NotificationType.RESERVATION_CREATED_WITH_ACCESS_CODE,
            attachments=[attachment])

    def send_access_code_created_mail(self):
        self.send_reservation_mail(
            NotificationType.RESERVATION_ACCESS_CODE_CREATED)

    def save(self, *args, **kwargs):
        self.duration = DateTimeTZRange(self.begin, self.end, '[)')

        if not self.access_code:
            access_code_type = self.resource.access_code_type
            if self.resource.is_access_code_enabled(
            ) and self.resource.generate_access_codes:
                self.access_code = generate_access_code(access_code_type)

        return super().save(*args, **kwargs)
Exemplo n.º 30
0
class TouristicContent(AddPropertyMixin, PublishableMixin, MapEntityMixin,
                       StructureRelated, TimeStampedModelMixin, PicturesMixin,
                       NoDeleteMixin):
    """ A generic touristic content (accomodation, museum, etc.) in the park
    """
    description_teaser = models.TextField(verbose_name=_("Description teaser"),
                                          blank=True,
                                          help_text=_("A brief summary"),
                                          db_column='chapeau')
    description = models.TextField(verbose_name=_("Description"),
                                   blank=True,
                                   db_column='description',
                                   help_text=_("Complete description"))
    themes = models.ManyToManyField(Theme,
                                    related_name="touristiccontents",
                                    db_table="t_r_contenu_touristique_theme",
                                    blank=True,
                                    verbose_name=_("Themes"),
                                    help_text=_("Main theme(s)"))
    geom = models.GeometryField(verbose_name=_("Location"), srid=settings.SRID)
    category = models.ForeignKey(TouristicContentCategory,
                                 related_name='contents',
                                 verbose_name=_("Category"),
                                 db_column='categorie')
    contact = models.TextField(verbose_name=_("Contact"),
                               blank=True,
                               db_column='contact',
                               help_text=_("Address, phone, etc."))
    email = models.EmailField(verbose_name=_("Email"),
                              max_length=256,
                              db_column='email',
                              blank=True,
                              null=True)
    website = models.URLField(verbose_name=_("Website"),
                              max_length=256,
                              db_column='website',
                              blank=True,
                              null=True)
    practical_info = models.TextField(verbose_name=_("Practical info"),
                                      blank=True,
                                      db_column='infos_pratiques',
                                      help_text=_("Anything worth to know"))
    type1 = models.ManyToManyField(TouristicContentType1,
                                   related_name='contents1',
                                   verbose_name=_("Type 1"),
                                   db_table="t_r_contenu_touristique_type1",
                                   blank=True)
    type2 = models.ManyToManyField(TouristicContentType2,
                                   related_name='contents2',
                                   verbose_name=_("Type 2"),
                                   db_table="t_r_contenu_touristique_type2",
                                   blank=True)
    source = models.ManyToManyField('common.RecordSource',
                                    blank=True,
                                    related_name='touristiccontents',
                                    verbose_name=_("Source"),
                                    db_table='t_r_contenu_touristique_source')
    portal = models.ManyToManyField('common.TargetPortal',
                                    blank=True,
                                    related_name='touristiccontents',
                                    verbose_name=_("Portal"),
                                    db_table='t_r_contenu_touristique_portal')
    eid = models.CharField(verbose_name=_("External id"),
                           max_length=1024,
                           blank=True,
                           null=True,
                           db_column='id_externe')
    reservation_system = models.ForeignKey(
        ReservationSystem,
        verbose_name=_("Reservation system"),
        blank=True,
        null=True)
    reservation_id = models.CharField(verbose_name=_("Reservation ID"),
                                      max_length=1024,
                                      blank=True,
                                      db_column='id_reservation')
    approved = models.BooleanField(verbose_name=_("Approved"),
                                   default=False,
                                   db_column='labellise')

    objects = NoDeleteMixin.get_manager_cls(models.GeoManager)()

    class Meta:
        db_table = 't_t_contenu_touristique'
        verbose_name = _("Touristic content")
        verbose_name_plural = _("Touristic contents")

    def __str__(self):
        return self.name

    @property
    def districts_display(self):
        return ', '.join([str(d) for d in self.districts])

    @property
    def type1_label(self):
        return self.category.type1_label

    @property
    def type2_label(self):
        return self.category.type2_label

    @property
    def type1_display(self):
        return ', '.join([str(n) for n in self.type1.all()])

    @property
    def type2_display(self):
        return ', '.join([str(n) for n in self.type2.all()])

    @property
    def prefixed_category_id(self):
        return self.category.prefixed_id

    def distance(self, to_cls):
        return settings.TOURISM_INTERSECTION_MARGIN

    @property
    def type(self):
        """Fake type to simulate POI for mobile app v1"""
        return self.category

    @property
    def min_elevation(self):
        return 0

    @property
    def max_elevation(self):
        return 0

    @property
    def portal_display(self):
        return ', '.join([str(portal) for portal in self.portal.all()])

    @property
    def source_display(self):
        return ','.join([str(source) for source in self.source.all()])

    @property
    def themes_display(self):
        return ','.join([str(source) for source in self.themes.all()])

    @property
    def extent(self):
        return self.geom.buffer(10).transform(settings.API_SRID,
                                              clone=True).extent

    @property
    def rando_url(self):
        category_slug = _('touristic-content')
        return '{}/{}/'.format(category_slug, self.slug)

    @property
    def meta_description(self):
        return plain_text(self.description_teaser or self.description)[:500]