Ejemplo n.º 1
0
class Person(models.Model):
    gender = models.CharField(max_length=1, choices=GENDER_CH)
    happy = models.BooleanField(default=True)
    unhappy = models.BooleanField(default=False)
    bipolar = 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_CHOCIES)
    try:
        uuid = models.UUIDField(primary_key=False)
    except AttributeError:
        # New at Django 1.9
        pass
    try:
        name_hash = models.BinaryField(max_length=16)
    except AttributeError:
        # We can't test the binary field if it is not supported
        # (django < 1,6)
        pass
    try:
        from django.contrib.postgres.fields import ArrayField
        acquaintances = ArrayField(models.IntegerField())
    except ImportError:
        # New at Django 1.9
        pass

    try:
        from django.contrib.postgres.fields import JSONField
        data = JSONField()
    except ImportError:
        # New at Django 1.9
        pass

    try:
        from django.contrib.postgres.fields import HStoreField
        hstore_data = HStoreField()
    except ImportError:
        # New at Django 1.8
        pass

    # backward compatibility with Django 1.1
    try:
        wanted_games_qtd = models.BigIntegerField()
    except AttributeError:
        wanted_games_qtd = models.IntegerField()

    try:
        from django.contrib.postgres.fields.citext import CICharField, CIEmailField, CITextField
        ci_char = CICharField(max_length=30)
        ci_email = CIEmailField()
        ci_text = CITextField()
    except ImportError:
        # New at Django 1.11
        pass

    try:
        duration_of_sleep = models.DurationField()
    except AttributeError:
        pass

    if MOMMY_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()
Ejemplo n.º 2
0
class LegFile(TimestampedModelMixin, models.Model):
    key = models.IntegerField(primary_key=True)
    id = models.CharField(max_length=100, null=True)
    contact = models.CharField(max_length=1000, default="No contact")
    controlling_body = models.CharField(max_length=1000)
    date_scraped = models.DateTimeField(auto_now_add=True,
                                        null=True,
                                        blank=True)
    last_scraped = models.DateTimeField(auto_now=True)
    final_date = models.DateField(null=True)
    intro_date = models.DateField(default=datetime.datetime.now)
    sponsors = models.ManyToManyField(CouncilMember,
                                      related_name='legislation')
    status = models.CharField(max_length=1000)
    title = models.TextField()
    type = models.CharField(max_length=1000)
    url = models.URLField()
    version = models.CharField(max_length=100)
    is_routine = models.BooleanField(default=True, blank=True)

    class Meta:
        ordering = ['-key']

    def __unicode__(self):
        return "%s %s: %s%s" % (self.type, self.id, self.title[:100],
                                '...' if len(self.title) > 100 else '')

    @models.permalink
    def get_absolute_url(self):
        return ('legislation_detail', [str(self.pk)])

    @property
    def last_action_date(self):
        """
        Gets the date of the latest action

        """
        actions = self.actions.all()

        if len(actions) == 0:
            return None

        return max([action.date_taken for action in actions])

    @property
    def timeline(self):
        """
        Gets a timeline object that represents the legfile actions grouped by
        ``date_taken``.

        """
        from collections import defaultdict

        class LegActionTimeline(defaultdict):
            def __init__(self):
                super(LegActionTimeline, self).__init__(list)

            def __iter__(self):
                return iter(sorted(self.keys()))

        timeline = LegActionTimeline()
        for action in self.actions.all().order_by('date_taken'):
            print action.date_taken
            timeline[action.date_taken].append(action)

        return timeline

    def all_text(self):
        if not hasattr(self, '_all_text'):
            att_text = [att.fulltext for att in self.attachments.all()]
            self._all_text = ' '.join([self.title] + att_text)
        return self._all_text

    def unique_words(self):
        """
        Gets all the white-space separated words in the file.  A word is
        anything that starts and ends with word characters and has no internal
        white space.

        """
        # Get rid of any punctuation on the outside of words.
        only_words = re.sub(r'(\s\W+|\W+\s|\W+$)', ' ', self.title)

        # Pick out and return the unique values by spliting on whitespace, and
        # lowercasing everything.
        unique_words = set(word.lower() for word in only_words.split())
        return unique_words

    def addresses(self):
        addresses = ebdata.nlp.addresses.parse_addresses(self.all_text())
        return addresses

    def topics(self):
        return settings.TOPIC_CLASSIFIER(self.title)

    def get_status_label(self):
        if self.status in [
                'Adopted', 'Approved', 'Direct Introduction', 'Passed'
        ]:
            return 'label-success'
        elif self.status in ['Failed to Pass', 'Vetoed']:
            return 'label-important'
        else:
            return 'label-inverse'

    def mentioned_legfiles(self):
        """
        Gets a generator for any files (specifically, bills) mentioned in the
        file.

        """
        # Find all the strings that match the characteristic regular expression
        # for a bill id.
        id_matches = re.findall(r'\s(\d{6}(-A+)?)', self.title)

        # The id matches may each have two groups (the second of which will
        # contain only the A's).  We only care about the first.
        mentioned_legfile_ids = set(groups[0] for groups in id_matches)

        for mentioned_legfile_id in mentioned_legfile_ids:
            # It's possible that no legfile in our database may match the id
            # we've parsed out.  When this is the case, there's nothing we can
            # do about it, so just fail "silently" (with a log message).
            try:
                mentioned_legfile = LegFile.objects.get(
                    id=mentioned_legfile_id)
                yield mentioned_legfile
            except LegFile.DoesNotExist:
                # TODO: Use a log message.
                print 'LegFile %r, referenced from key %s, does not exist!!!' % (
                    mentioned_legfile_id, self.pk)

    def update(self, attribs, commit=True, **save_kwargs):
        for attr, val in attribs.items():
            setattr(self, attr, val)

        if commit:
            return self.save(**save_kwargs)

    def save(self,
             update_words=True,
             update_mentions=True,
             update_locations=True,
             update_topics=True,
             *args,
             **kwargs):
        """
        Calls the default ``Models.save()`` method, and creates or updates
        metadata for the legislative file as well.

        """
        super(LegFile, self).save(*args, **kwargs)

        metadata = LegFileMetaData.objects.get_or_create(legfile=self)[0]

        if update_words:
            # Add the unique words to the metadata
            metadata.words.clear()
            unique_words = self.unique_words()
            for word in unique_words:
                md_word = MetaData_Word.objects.get_or_create(value=word)[0]
                metadata.words.add(md_word)

        if update_locations:
            # Add the unique locations to the metadata
            metadata.locations.clear()
            locations = self.addresses()
            for location in locations:
                try:
                    md_location = MetaData_Location.objects.get_or_create(
                        address=location[0])[0]
                except MetaData_Location.CouldNotBeGeocoded:
                    continue

                metadata.locations.add(md_location)

        if update_mentions:
            # Add the mentioned files to the metadata
            metadata.mentioned_legfiles.clear()
            for mentioned_legfile in self.mentioned_legfiles():
                metadata.mentioned_legfiles.add(mentioned_legfile)

        if update_topics:
            # Add topics to the metadata
            metadata.topics.clear()
            for topic in self.topics():
                t = MetaData_Topic.objects.get_or_create(topic=topic)[0]
                metadata.topics.add(t)

        metadata.save()

    def get_data_source(self):
        return PhillyLegistarSiteWrapper()

    def refresh(self, stale_time=datetime.timedelta(days=1), force=False):
        """
        Update the file if it has not been updated in a while.  The "while" is
        dictated the `stale_time` parameter, a `timedelta`.  If `force` is True,
        then the refresh will happen immediately, regardless of the time it was
        last updated.
        """
        pass
Ejemplo n.º 3
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=_(u"Description teaser"),
        blank=True,
        help_text=_(u"A brief summary"),
        db_column='chapeau')
    description = models.TextField(verbose_name=_(u"Description"),
                                   blank=True,
                                   db_column='description',
                                   help_text=_(u"Complete description"))
    themes = models.ManyToManyField(Theme,
                                    related_name="touristic_events",
                                    db_table="t_r_evenement_touristique_theme",
                                    blank=True,
                                    verbose_name=_(u"Themes"),
                                    help_text=_(u"Main theme(s)"))
    geom = models.PointField(verbose_name=_(u"Location"), srid=settings.SRID)
    begin_date = models.DateField(blank=True,
                                  null=True,
                                  verbose_name=_(u"Begin date"),
                                  db_column='date_debut')
    end_date = models.DateField(blank=True,
                                null=True,
                                verbose_name=_(u"End date"),
                                db_column='date_fin')
    duration = models.CharField(verbose_name=_(u"Duration"),
                                max_length=64,
                                blank=True,
                                db_column='duree',
                                help_text=_(u"3 days, season, ..."))
    meeting_point = models.CharField(verbose_name=_(u"Meeting point"),
                                     max_length=256,
                                     blank=True,
                                     db_column='point_rdv',
                                     help_text=_(u"Where exactly ?"))
    meeting_time = models.TimeField(verbose_name=_(u"Meeting time"),
                                    blank=True,
                                    null=True,
                                    db_column='heure_rdv',
                                    help_text=_(u"11:00, 23:30"))
    contact = models.TextField(verbose_name=_(u"Contact"),
                               blank=True,
                               db_column='contact')
    email = models.EmailField(verbose_name=_(u"Email"),
                              max_length=256,
                              db_column='email',
                              blank=True,
                              null=True)
    website = models.URLField(verbose_name=_(u"Website"),
                              max_length=256,
                              db_column='website',
                              blank=True,
                              null=True)
    organizer = models.CharField(verbose_name=_(u"Organizer"),
                                 max_length=256,
                                 blank=True,
                                 db_column='organisateur')
    speaker = models.CharField(verbose_name=_(u"Speaker"),
                               max_length=256,
                               blank=True,
                               db_column='intervenant')
    type = models.ForeignKey(TouristicEventType,
                             verbose_name=_(u"Type"),
                             blank=True,
                             null=True,
                             db_column='type')
    accessibility = models.CharField(verbose_name=_(u"Accessibility"),
                                     max_length=256,
                                     blank=True,
                                     db_column='accessibilite')
    participant_number = models.CharField(
        verbose_name=_(u"Number of participants"),
        max_length=256,
        blank=True,
        db_column='nb_places')
    booking = models.TextField(verbose_name=_(u"Booking"),
                               blank=True,
                               db_column='reservation')
    target_audience = models.CharField(verbose_name=_(u"Target audience"),
                                       max_length=128,
                                       blank=True,
                                       null=True,
                                       db_column='public_vise')
    practical_info = models.TextField(
        verbose_name=_(u"Practical info"),
        blank=True,
        db_column='infos_pratiques',
        help_text=_(u"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=_(u"External id"),
                           max_length=128,
                           blank=True,
                           null=True,
                           db_column='id_externe')
    approved = models.BooleanField(verbose_name=_(u"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 = _(u"Touristic event")
        verbose_name_plural = _(u"Touristic events")
        ordering = ['-begin_date']

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_document_public_url(self):
        """ Override ``geotrek.common.mixins.PublishableMixin``
        """
        return ('tourism:touristicevent_document_public', [], {
            'lang': get_language(),
            'pk': self.pk,
            'slug': self.slug
        })

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

    @property
    def type2(self):
        return []

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

    @property
    def dates_display(self):
        if not self.begin_date and not self.end_date:
            return u""
        elif not self.end_date:
            return _(u"starting from {begin}").format(
                begin=date_format(self.begin_date, 'SHORT_DATE_FORMAT'))
        elif not self.begin_date:
            return _(u"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 _(u"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([unicode(portal) for portal in self.portal.all()])

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

    @property
    def themes_display(self):
        return ','.join([unicode(source) for source in self.themes.all()])
Ejemplo n.º 4
0
class User(AbstractBaseUser):

    # main
    name = models.CharField(max_length=60, blank=True, null=True)
    email = models.EmailField(max_length=60,
                              unique=True)  #verbose_name="email"
    username = models.CharField(_('username'),
                                max_length=150,
                                unique=True,
                                validators=[UnicodeUsernameValidator()])
    image = models.ImageField(default="default-profile.png",
                              upload_to="profile_pics",
                              blank=True)

    # contact info
    phones = models.JSONField(default=list, null=True, blank=True)
    website = models.URLField(blank=True, null=True)
    about = models.CharField(max_length=280, blank=True, null=True)
    location = models.PointField(blank=True, null=True)
    address = models.CharField(max_length=200, blank=True, null=True)

    # additional info
    genre = models.ManyToManyField(Genre, related_name='users')
    job = models.ManyToManyField(Job, blank=True, related_name="users")
    instrument = models.ManyToManyField(Instrument,
                                        blank=True,
                                        related_name="users")
    # budget = models.JSONField(default=list, validators=[validate_budget])
    budget_from = models.BigIntegerField(null=True)
    budget_to = models.BigIntegerField(null=True)

    # Social Media
    instagram = models.CharField(max_length=60, blank=True, null=True)
    twitter = models.CharField(max_length=60, blank=True, null=True)
    facebook = models.CharField(max_length=60, blank=True, null=True)
    tiktok = models.CharField(max_length=60, blank=True, null=True)
    youtube = models.CharField(max_length=60, blank=True, null=True)
    soundcloud = models.CharField(max_length=60, blank=True, null=True)
    spotify = models.CharField(max_length=60, blank=True, null=True)
    tidal = models.CharField(max_length=60, blank=True, null=True)
    deezer = models.CharField(max_length=60, blank=True, null=True)

    # REQUIRED
    date_joined = models.DateTimeField(auto_now_add=True)
    last_login = models.DateTimeField(auto_now=True)
    is_admin = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    #Rating field
    ratings = models.ManyToManyField('self',
                                     through='Rating',
                                     symmetrical=False,
                                     related_name='rated_by')
    objects = Manager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = [
        "username",
    ]

    # class Meta:
    #     ordering = ['field_name']

    # def __str__(self):
    # return f"{self.field_name} {self.field_name}"

    def has_perm(self, perm, obj=None):
        return self.is_admin

    def has_module_perms(self, app_label):
        return True

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
Ejemplo n.º 5
0
class Layer(models.Model):
    """
    A layer object that can be added to any map.
    """

    name = models.CharField(
        max_length=200, help_text='Name that will be displayed within GeoQ')
    type = models.CharField(choices=SERVICE_TYPES, max_length=75)
    url = models.CharField(
        help_text=
        'URL of service. If WMS or ESRI, can be any valid URL. Otherwise, the URL will require a local proxy',
        max_length=500)
    layer = models.CharField(
        max_length=800,
        null=True,
        blank=True,
        help_text=
        'Layer names can sometimes be comma-separated, and are not needed for data layers (KML, GeoRSS, GeoJSON...)'
    )
    image_format = models.CharField(
        null=True,
        blank=True,
        choices=IMAGE_FORMATS,
        max_length=75,
        help_text=
        'The MIME type of the image format to use for tiles on WMS layers (image/png, image/jpeg image/gif...). Double check that the server exposes this exactly - some servers push png instead of image/png.'
    )
    styles = models.CharField(
        null=True,
        blank=True,
        max_length=200,
        help_text=
        'The name of a style to use for this layer (only useful for WMS layers if the server exposes it.)'
    )
    transparent = models.BooleanField(
        default=True,
        help_text=
        'If WMS or overlay, should the tiles be transparent where possible?')
    refreshrate = models.PositiveIntegerField(
        blank=True,
        null=True,
        verbose_name="Layer Refresh Rate",
        help_text=
        'Layer refresh rate in seconds for vector/data layers (will not refresh WMS layers)'
    )
    description = models.TextField(
        max_length=800,
        null=True,
        blank=True,
        help_text=
        'Text to show in layer chooser, please be descriptive - this will soon be searchable'
    )
    attribution = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text=
        "Attribution from layers to the map display (will show in bottom of map when layer is visible)."
    )
    token = models.CharField(
        max_length=400,
        null=True,
        blank=True,
        help_text=
        'Authentication token, if required (usually only for secure layer servers)'
    )

    ## Advanced layer options
    objects = models.GeoManager()
    extent = models.PolygonField(null=True,
                                 blank=True,
                                 help_text='Extent of the layer.')
    layer_parsing_function = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        help_text=
        'Advanced - The javascript function used to parse a data service (GeoJSON, GeoRSS, KML), needs to be an internally known parser. Contact an admin if you need data parsed in a new way.'
    )
    enable_identify = models.BooleanField(
        default=False,
        help_text=
        'Advanced - Allow user to click map to query layer for details. The map server must support queries for this layer.'
    )
    info_format = models.CharField(
        max_length=75,
        null=True,
        blank=True,
        choices=INFO_FORMATS,
        help_text='Advanced - what format the server returns for an WMS-I query'
    )
    root_field = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        help_text=
        'Advanced - For WMS-I (queryable) layers, the root field returned by server. Leave blank for default (will usually be "FIELDS" in returned XML).'
    )
    fields_to_show = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text=
        'Fields to show when someone uses the identify tool to click on the layer. Leave blank for all.'
    )
    downloadableLink = models.URLField(
        max_length=400,
        null=True,
        blank=True,
        help_text=
        'URL of link to supporting tool (such as a KML document that will be shown as a download button)'
    )
    layer_params = JSONField(
        null=True,
        blank=True,
        help_text=
        'JSON key/value pairs to be sent to the web service.  ex: {"crs":"urn:ogc:def:crs:EPSG::4326"}'
    )
    dynamic_params = JSONField(
        null=True,
        blank=True,
        help_text=
        'URL Variables that may be modified by the analyst. ex: "date"')
    spatial_reference = models.CharField(
        max_length=32,
        blank=True,
        null=True,
        default="EPSG:4326",
        help_text=
        'The spatial reference of the service.  Should be in ESPG:XXXX format.'
    )
    constraints = models.TextField(
        null=True,
        blank=True,
        help_text='Constrain layer data displayed to certain feature types')
    disabled = models.BooleanField(
        default=False,
        blank=True,
        help_text="If unchecked, Don't show this layer when listing all layers"
    )
    layer_info_link = models.URLField(
        null=True,
        blank=True,
        help_text='URL of info about the service, or a help doc or something',
        max_length=500)
    created_at = models.DateTimeField(auto_now_add=True, null=True)
    updated_at = models.DateTimeField(auto_now_add=True, null=True)

    ## Primarily for http://trac.osgeo.org/openlayers/wiki/OpenLayersOptimization
    additional_domains = models.TextField(
        null=True,
        blank=True,
        help_text=
        'Semicolon seperated list of additional domains for the layer. Only used if you want to cycle through domains for load-balancing'
    )

    def __unicode__(self):
        return '{0}'.format(self.name)

    def get_layer_urls(self):
        """
        Returns a list of urls for the layer.
        """
        urls = []

        if getattr(self, 'additional_domains'):
            map(urls.append,
                (domain
                 for domain in self.additional_domains.split(";") if domain))

        return urls

    def get_absolute_url(self):
        return reverse('layer-update', args=[self.id])

    def get_layer_params(self):
        """
        Returns the layer_params attribute, which should be json
        """
        return self.layer_params

    def layer_json(self):
        return {
            "id": self.id,
            "name": self.name,
            "format": self.image_format,
            "type": self.type,
            "url": self.url,
            "subdomains": self.get_layer_urls(),
            "layer": self.layer,
            "transparent": self.transparent,
            "layerParams": self.layer_params,
            "dynamicParams": self.dynamic_params,
            "refreshrate": self.refreshrate,
            "token": self.token,
            "attribution": self.attribution,
            "spatialReference": self.spatial_reference,
            "layerParsingFunction": self.layer_parsing_function,
            "enableIdentify": self.enable_identify,
            "rootField": self.root_field,
            "infoFormat": self.info_format,
            "fieldsToShow": self.fields_to_show,
            "description": self.description,
            "downloadableLink": self.downloadableLink,
            "layer_info_link": self.layer_info_link,
            "styles": self.styles,
        }

    class Meta:
        ordering = ["name"]
Ejemplo n.º 6
0
class Topology(BaseDate):
    name = models.CharField(_('name'), max_length=75, unique=True)
    format = models.CharField(_('format'),
                              choices=PARSERS,
                              max_length=128,
                              help_text=_('Select topology format'))
    url = models.URLField(_('url'),
                          help_text=_('URL where topology will be retrieved'))

    class Meta:
        app_label = 'links'
        verbose_name_plural = _('topologies')

    def __unicode__(self):
        return self.name

    _parser = None

    @property
    def parser(self):
        if not self._parser:
            self._parser = import_by_path(self.format)
        return self._parser

    @property
    def is_layer2(self):
        return self.format == 'netdiff.BatmanParser'

    @property
    def latest(self):
        return self.parser(self.url)

    def diff(self):
        """ shortcut to netdiff.diff """
        latest = self.latest
        current = NetJsonParser(self.json())
        return diff(current, latest)

    def json(self):
        """ returns a dict that represents a NetJSON NetworkGraph object """
        nodes = []
        links = []

        for link in self.link_set.all():
            if self.is_layer2:
                source = link.interface_a.mac
                destination = link.interface_b.mac
            else:
                source = str(link.interface_a.ip_set.first().address)
                destination = str(link.interface_b.ip_set.first().address)
            nodes.append({'id': source})
            nodes.append({'id': destination})
            links.append(
                OrderedDict((('source', source), ('target', destination),
                             ('weight', link.metric_value))))

        return OrderedDict(
            (('type', 'NetworkGraph'), ('protocol', self.parser.protocol),
             ('version', self.parser.version), ('metric', self.parser.metric),
             ('nodes', nodes), ('links', links)))

    def update(self):
        """
        Updates topology
        Links are not deleted straightaway but set as "disconnected"
        """
        from .link import Link  # avoid circular dependency
        diff = self.diff()

        status = {
            'added': 'active',
            'removed': 'disconnected',
            'changed': 'active'
        }

        for section in ['added', 'removed', 'changed']:
            # section might be empty
            if not diff[section]:
                continue
            for link_dict in diff[section]['links']:
                try:
                    link = Link.get_or_create(source=link_dict['source'],
                                              target=link_dict['target'],
                                              weight=link_dict['weight'],
                                              topology=self)
                except LinkDataNotFound as e:
                    msg = 'Exception while updating {0}'.format(
                        self.__repr__())
                    logger.exception(msg)
                    print('{0}\n{1}\n'.format(msg, e))
                    continue
                link.ensure(status=status[section], weight=link_dict['weight'])
Ejemplo n.º 7
0
class DataResource(Displayable):
    """Represents a file that has been uploaded to Geoanalytics for representation"""
    original_file = models.FileField(upload_to='geographica_resources',
                                     null=True,
                                     blank=True)
    resource_file = models.FileField(upload_to='geographica_resources',
                                     null=True,
                                     blank=True)
    resource_url = models.URLField(null=True, blank=True)
    metadata_url = models.URLField(null=True, blank=True)
    metadata_xml = models.TextField(null=True, blank=True)
    driver_config = DictionaryField(null=True, blank=True)
    metadata_properties = DictionaryField(null=True, blank=True)
    last_change = models.DateTimeField(null=True, blank=True, auto_now=True)
    last_refresh = models.DateTimeField(
        null=True, blank=True
    )  # updates happen only to geocms that were not uploaded by the user.
    next_refresh = models.DateTimeField(
        null=True, blank=True,
        db_index=True)  # will be populated every time the update manager runs
    refresh_every = TimedeltaField(null=True, blank=True)
    md5sum = models.CharField(max_length=64, blank=True,
                              null=True)  # the unique md5 sum of the data
    bounding_box = models.PolygonField(null=True, srid=4326, blank=True)
    import_log = models.TextField(null=True, blank=True)
    associated_pages = models.ManyToManyField("pages.Page",
                                              blank=True,
                                              null=True,
                                              related_name='data_resources')

    driver = models.CharField(
        default='terrapyn.geocms.drivers.spatialite',
        max_length=255,
        null=False,
        blank=False,
        choices=getattr(settings, 'INSTALLED_DATARESOURCE_DRIVERS', (
            ('terrapyn.geocms.drivers.spatialite',
             'Spatialite (universal vector)'),
            ('terrapyn.geocms.drivers.shapefile', 'Shapefile'),
            ('terrapyn.geocms.drivers.geotiff', 'GeoTIFF'),
            ('terrapyn.geocms.drivers.postgis', 'PostGIS'),
            ('terrapyn.geocms.drivers.kmz', 'Google Earth KMZ'),
            ('terrapyn.geocms.drivers.ogr', 'OGR DataSource'),
        )))

    big = models.BooleanField(
        default=False,
        help_text='Set this to be true if the dataset is more than 100MB'
    )  # causes certain drivers to optimize for datasets larger than memory

    def get_absolute_url(self):
        return reverse('resource-page', kwargs={'slug': self.slug})

    def get_admin_url(self):
        return reverse("admin:geocms_dataresource_change", args=(self.id, ))

    @property
    def srs(self):
        if not self.metadata.native_srs:
            self.driver_instance.compute_spatial_metadata()
        srs = osr.SpatialReference()
        srs.ImportFromProj4(self.metadata.native_srs.encode('ascii'))
        return srs

    @property
    def driver_instance(self):
        if not hasattr(self, '_driver_instance'):
            self._driver_instance = get_driver(self.driver)(self)
        return self._driver_instance

    def __unicode__(self):
        return self.title

    class Meta:
        permissions = (
            ('view_dataresource',
             "View data resource"),  # to add beyond the default
        )
Ejemplo n.º 8
0
class Profile(BaseModel):
    """
    """
    active = models.BooleanField(default=True)
    app_admin = models.BooleanField(default=False)
    is_contact = models.BooleanField(default=False)
    notify = models.BooleanField(default=True)
    published = models.BooleanField(default=False)
    dashboard_override = models.BooleanField(
        'Override Default Dashboard Settings', default=False)
    dashboard_choices = MultiSelectField('Dashboard Choices',
                                         choices=DASHBOARD_CHOICES,
                                         null=True,
                                         blank=True)
    editor = models.CharField(max_length=8,
                              choices=EDITOR_CHOICES,
                              null=True,
                              blank=True)
    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE,
                                blank=True,
                                null=True)
    icon_size = models.CharField(max_length=255,
                                 blank=True,
                                 null=True,
                                 choices=ICON_CHOICES)
    icon_color = models.CharField(max_length=255,
                                  blank=True,
                                  null=True,
                                  choices=COLOR_CHOICES)
    page_size = models.PositiveIntegerField(blank=True, null=True)
    preferred_username = models.CharField('Preferred Username',
                                          max_length=150,
                                          blank=True,
                                          null=True)
    rate = models.DecimalField('Hourly Rate (United States Dollar - USD)',
                               blank=True,
                               null=True,
                               max_digits=12,
                               decimal_places=2)
    unit = models.DecimalField("Unit",
                               default=1.0,
                               blank=True,
                               null=True,
                               max_digits=12,
                               decimal_places=2)
    avatar_url = models.URLField("Avatar URL", blank=True, null=True)
    bio = models.TextField(blank=True, null=True)
    address = models.TextField(blank=True, null=True)
    preferred_payment_method = models.CharField('Preferred Payment Method',
                                                max_length=255,
                                                blank=True,
                                                null=True,
                                                choices=PAYMENT_CHOICES)

    def __str__(self):
        if self.user:
            return self.user.username
        else:
            return '-'.join([self._meta.verbose_name, str(self.pk)])

    def get_avatar_url(self):
        if self.avatar_url is not None:
            return self.avatar_url
        else:
            return gravatar_url(self.user.email)

    def get_username(self):
        if self.preferred_username is not None:
            return self.preferred_username
        elif self.user:
            return self.user.username
        else:
            return '-'.join([self._meta.verbose_name, str(self.pk)])
Ejemplo n.º 9
0
class Time(BaseModel):
    """
    Date, Client, Project, Project Code, Task, Notes, Hours, Billable?,
    Invoiced?, First Name, Last Name, Department, Employee?, Billable
    Rate, Billable Amount, Cost Rate, Cost Amount, Currency,
    External Reference URL
    """
    billable = models.BooleanField(default=True)
    employee = models.BooleanField(default=True)
    invoiced = models.BooleanField(default=False)
    client = models.ForeignKey(
        Client,
        blank=True,
        null=True,
        limit_choices_to={'active': True},
    )
    project = models.ForeignKey(
        Project,
        blank=True,
        null=True,
        limit_choices_to={'active': True},
    )
    task = models.ForeignKey(
        Task,
        blank=True,
        null=True,
        limit_choices_to={'active': True},
    )
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             blank=True,
                             null=True,
                             limit_choices_to={'profile__active': True})
    estimate = models.ForeignKey(Estimate,
                                 blank=True,
                                 null=True,
                                 on_delete=models.SET_NULL)
    invoice = models.ForeignKey(Invoice,
                                blank=True,
                                null=True,
                                on_delete=models.SET_NULL,
                                limit_choices_to={'last_payment_date': None})
    date = models.DateField(default=timezone.now)
    hours = models.DecimalField("Hours",
                                default=1.0,
                                blank=True,
                                null=True,
                                max_digits=12,
                                decimal_places=2)
    first_name = models.CharField(max_length=300, blank=True, null=True)
    last_name = models.CharField(max_length=300, blank=True, null=True)
    department = models.CharField(max_length=300, blank=True, null=True)
    cost_rate = models.DecimalField(blank=True,
                                    null=True,
                                    max_digits=12,
                                    decimal_places=2)
    cost_amount = models.DecimalField(blank=True,
                                      null=True,
                                      max_digits=12,
                                      decimal_places=2)
    currency = models.CharField(max_length=300, blank=True, null=True)
    external_reference_url = models.URLField(blank=True, null=True)
    project_code = models.IntegerField(blank=True, null=True)
    log = models.TextField(blank=True, null=True)

    def __str__(self):
        return '-'.join([self._meta.verbose_name, str(self.pk)])

    # https://docs.djangoproject.com/en/1.9/ref/models/instances/#get-absolute-url
    def get_absolute_url(self, hostname):
        return '%s/%s' % (hostname, reverse('time_view', args=[str(self.id)]))
Ejemplo n.º 10
0
class Event(MPTTModel, BaseModel, SchemalessFieldMixin):
    jsonld_type = "Event/LinkedEvent"
    """
    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'

    SUPER_EVENT_TYPES = ((SuperEventType.RECURRING, _('Recurring')),
                         # Other types include e.g. a festival
                         )

    # 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)
    publisher = models.ForeignKey('django_orghierarchy.Organization',
                                  verbose_name=_('Publisher'),
                                  db_index=True,
                                  on_delete=models.PROTECT,
                                  related_name='published_events')

    # 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)

    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,
                                        default=None,
                                        choices=SUPER_EVENT_TYPES)

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

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

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

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

    class MPTTMeta:
        parent_attr = 'super_event'

    def save(self, *args, **kwargs):
        # needed to cache location event numbers
        old_location = None
        if self.id:
            try:
                old_location = Event.objects.get(id=self.id).location
            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.'
                      )
                })

        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)

    def __str__(self):
        name = ''
        for lang in settings.LANGUAGES:
            s = getattr(self, 'name_%s' % lang[0], 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)
Ejemplo n.º 11
0
class AdvertisingCampaign(models.Model):
    name = models.CharField(max_length=128)
    account = models.ForeignKey('accounts.Account')
    venue_account = models.ForeignKey('accounts.VenueAccount',
                                      blank=True,
                                      null=True,
                                      on_delete=models.SET_NULL)
    all_of_canada = models.BooleanField()
    regions = models.ManyToManyField(Region)

    budget = MoneyField(max_digits=10,
                        decimal_places=2,
                        default_currency='CAD')
    ammount_spent = MoneyField(max_digits=18,
                               decimal_places=10,
                               default_currency='CAD')

    enough_money = models.BooleanField(default=False)

    started = models.DateTimeField(auto_now=True, auto_now_add=True)
    ended = models.DateTimeField(auto_now=False,
                                 auto_now_add=False,
                                 null=True,
                                 blank=True)

    active_from = models.DateTimeField('active to',
                                       null=True,
                                       blank=True,
                                       auto_now=False,
                                       auto_now_add=False)
    active_to = models.DateTimeField('active to',
                                     null=True,
                                     blank=True,
                                     auto_now=False,
                                     auto_now_add=False)

    website = models.URLField()

    free = models.BooleanField(default=False)

    objects = money_manager(models.Manager())
    admin = AdminAdvertisingCampaignManager()
    with_unused_money = AdvertisingCampaignWithUnsusedMoney()

    active = ActiveAdvertisingCampaign()
    expired = ExpiredAdvertisingCampaign()

    def save(self, *args, **kwargs):
        if self.enough_money and self.ammount_spent >= self.budget and self.budget > Money(
                0, CAD):
            inform_user_that_money_was_spent(self)

        if self.ammount_spent >= self.budget:
            self.enough_money = False
        else:
            self.enough_money = True

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

    def __unicode__(self):
        return self.name

    def ammount_remaining(self):
        return self.budget - self.ammount_spent

    def regions_representation(self):
        return ", ".join(self.regions.all().values_list("name", flat=True))

    def is_active(self):
        now = datetime.datetime.now()
        return (self.enough_money or self.free) and (
            not self.active_from
            or self.active_from < now) and (not self.active_to
                                            or self.active_to > now)

    def is_finished(self):
        return self.active_to and self.active_to < datetime.datetime.now()

    def is_future(self):
        return self.active_from and self.active_from > datetime.datetime.now()
Ejemplo n.º 12
0
class Place(MPTTModel, BaseModel, SchemalessFieldMixin):
    publisher = models.ForeignKey('django_orghierarchy.Organization',
                                  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',
                            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', related_name='aliases', null=True)
    divisions = models.ManyToManyField(AdministrativeDivision,
                                       verbose_name=_('Divisions'),
                                       related_name='places',
                                       blank=True)

    geo_objects = models.GeoManager()
    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.replaced_by and self.replaced_by.replaced_by == self:
            raise Exception(
                "Trying to replace the location replacing this location by this location."
                "Please refrain from creating circular replacements and"
                "remove either one of the replacements."
                "We don't want homeless events.")

        # 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 = AdministrativeDivision.objects.filter(
                type__type__in=('district', 'sub_district', 'neighborhood',
                                'muni'),
                geometry__boundary__contains=self.position)
        else:
            self.divisions.clear()
Ejemplo n.º 13
0
class Image(models.Model):
    jsonld_type = 'ImageObject'

    # Properties from schema.org/Thing
    name = models.CharField(verbose_name=_('Name'),
                            max_length=255,
                            db_index=True,
                            default='')

    data_source = models.ForeignKey(DataSource,
                                    related_name='provided_%(class)s_data',
                                    db_index=True,
                                    null=True)
    publisher = models.ForeignKey('django_orghierarchy.Organization',
                                  verbose_name=_('Publisher'),
                                  db_index=True,
                                  null=True,
                                  blank=True,
                                  related_name='Published_images')

    created_time = models.DateTimeField(auto_now_add=True)
    last_modified_time = models.DateTimeField(auto_now=True, db_index=True)
    created_by = models.ForeignKey(User,
                                   null=True,
                                   blank=True,
                                   related_name='EventImage_created_by')
    last_modified_by = models.ForeignKey(
        User,
        related_name='EventImage_last_modified_by',
        null=True,
        blank=True)

    image = models.ImageField(upload_to='images', null=True, blank=True)
    url = models.URLField(verbose_name=_('Image'),
                          max_length=400,
                          null=True,
                          blank=True)
    cropping = ImageRatioField('image', '800x800', verbose_name=_('Cropping'))
    license = models.ForeignKey(License,
                                verbose_name=_('License'),
                                related_name='images',
                                default='cc_by')
    photographer_name = models.CharField(verbose_name=_('Photographer name'),
                                         max_length=255,
                                         null=True,
                                         blank=True)

    def save(self, *args, **kwargs):
        if not self.publisher:
            try:
                self.publisher = self.created_by.get_default_organization()
            except AttributeError:
                pass
        # ensure that either image or url is provided
        if not self.url and not self.image:
            raise ValidationError(_('You must provide either image or url.'))
        if self.url and self.image:
            raise ValidationError(
                _('You can only provide image or url, not both.'))
        self.last_modified_time = BaseModel.now()
        super(Image, self).save(*args, **kwargs)

    def is_user_editable(self):
        return self.data_source.user_editable

    def is_user_edited(self):
        return bool(self.data_source.user_editable and self.last_modified_by)

    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.is_admin(self.publisher)
Ejemplo n.º 14
0
class RasterLayer(models.Model, ValueCountMixin):
    """
    Source data model for raster layers
    """
    CONTINUOUS = 'co'
    CATEGORICAL = 'ca'
    MASK = 'ma'
    RANK_ORDERED = 'ro'

    DATATYPES = (
        (CONTINUOUS, 'Continuous'),
        (CATEGORICAL, 'Categorical'),
        (MASK, 'Mask'),
        (RANK_ORDERED, 'Rank Ordered'),
    )

    name = models.CharField(max_length=100, blank=True, null=True)
    description = models.TextField(blank=True, null=True)
    datatype = models.CharField(max_length=2, choices=DATATYPES, default='co')
    rasterfile = models.FileField(upload_to='rasters', null=True, blank=True)
    source_url = models.URLField(
        default='',
        blank=True,
        max_length=2500,
        help_text='External url to get the raster file from. If a value is set,'
        'the rasterfile field will be ignored.')
    nodata = models.CharField(
        max_length=100,
        null=True,
        blank=True,
        help_text=
        'Leave blank to keep the internal band nodata values. If a nodata '
        'value is specified here, it will be used for all bands of this raster.'
    )
    srid = models.IntegerField(
        null=True,
        blank=True,
        help_text='Leave blank to use the internal raster srid. If a srid is '
        'specified here, it will be used for all calculations.')
    max_zoom = models.IntegerField(
        null=True,
        blank=True,
        help_text='Leave blank to automatically determine the max zoom level '
        'from the raster scale. Otherwise the raster parsed up to '
        'the zoom level specified here.')
    build_pyramid = models.BooleanField(
        default=True,
        help_text='Should the tile pyramid be built? If unchecked, tiles will '
        'only be generated at the max zoom level.')
    next_higher = models.BooleanField(
        default=True,
        help_text=
        'Compared to the scale of the rasterlayer, use the next-higher '
        'zoomlevel as max zoom? If unchecked, the next-lower zoom level '
        'is used. This flag is ignored if the max_zoom is manually '
        'specified.')
    store_reprojected = models.BooleanField(
        default=True,
        help_text='Should the reprojected raster be stored? If unchecked, the '
        'reprojected version of the raster is not stored.')
    legend = models.ForeignKey(Legend, blank=True, null=True)
    modified = models.DateTimeField(auto_now=True)

    def __str__(self):
        return '{} {} (type: {})'.format(self.id, self.name, self.datatype)

    @property
    def discrete(self):
        """
        Returns true for discrete rasters.
        """
        return self.datatype in (self.CATEGORICAL, self.MASK,
                                 self.RANK_ORDERED)

    _bbox = None
    _bbox_srid = None

    def extent(self, srid=WEB_MERCATOR_SRID):
        """
        Returns bbox for layer.
        """
        if not self._bbox or self._bbox_srid != srid:
            # Get bbox for raster in original coordinates
            meta = self.metadata
            xmin = meta.uperleftx
            ymax = meta.uperlefty
            xmax = xmin + meta.width * meta.scalex
            ymin = ymax + meta.height * meta.scaley

            # Create Polygon box
            geom = OGRGeometry(Envelope((xmin, ymin, xmax, ymax)).wkt)

            # Set original srs
            if meta.srs_wkt:
                geom.srs = SpatialReference(meta.srs_wkt)
            else:
                geom.srid = meta.srid

            # Transform to requested srid
            geom.transform(srid)

            # Calculate value range for bbox
            coords = geom.coords[0]
            xvals = [x[0] for x in coords]
            yvals = [x[1] for x in coords]

            # Set bbox
            self._bbox = (min(xvals), min(yvals), max(xvals), max(yvals))
            self._bbox_srid = srid
        return self._bbox

    def index_range(self, zoom):
        """
        Compute the index range for this rasterlayer at a given zoom leve.
        """
        return self.rastertile_set.filter(tilez=zoom).aggregate(
            Min('tilex'), Max('tilex'), Min('tiley'), Max('tiley'))
Ejemplo n.º 15
0
class WMTSBasemap(Basemap):
    url = models.URLField(max_length=500, help_text="Capabilities xml url")
    layer = models.CharField(max_length=100)
    tile_matrix_set = models.CharField(max_length=100)
Ejemplo n.º 16
0
class InformationDesk(models.Model):

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

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

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

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

    def __unicode__(self):
        return self.name

    def __json__(self):
        return {
            'name': self.name,
            'description': self.description,
            'phone': self.phone,
            'email': self.email,
            'website': self.website,
            'photo_url': self.photo_url,
            'street': self.street,
            'postal_code': self.postal_code,
            'municipality': self.municipality,
            'latitude': self.latitude,
            'longitude': self.longitude,
        }

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

    @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 photo_url(self):
        if not self.photo:
            return None
        thumbnailer = get_thumbnailer(self.photo)
        try:
            thumb_detail = thumbnailer.get_thumbnail(aliases.get('thumbnail'))
            thumb_url = os.path.join(settings.MEDIA_URL, thumb_detail.name)
        except InvalidImageFormatError:
            thumb_url = None
            logger.error(
                _("Image %s invalid or missing from disk.") % self.photo)
        return thumb_url
Ejemplo n.º 17
0
class VectorTileBasemap(Basemap):
    url = models.URLField(max_length=1000, help_text="Vector tile url")
    api_key = models.CharField(max_length=200)
Ejemplo n.º 18
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=_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)

    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()))
Ejemplo n.º 19
0
class Resource(models.Model):
    """Modèle de classe d'une ressource de données."""
    class Meta(object):
        verbose_name = 'Ressource'
        verbose_name_plural = 'Ressources'

    # Managers
    # ========

    objects = models.Manager()
    default = DefaultResourceManager()

    # Champs atributaires
    # ===================

    ckan_id = models.UUIDField(
        verbose_name='Ckan UUID',
        default=uuid.uuid4,
        editable=False,
        unique=True,
    )

    title = models.TextField(verbose_name='Title', )

    description = models.TextField(
        verbose_name='Description',
        blank=True,
        null=True,
    )

    ftp_file = models.FileField(
        verbose_name='Fichier déposé sur sFTP',
        blank=True,
        null=True,
        upload_to=_ftp_file_upload_to,
        max_length=255,
    )

    referenced_url = models.URLField(
        verbose_name='Référencer une URL',
        max_length=2000,
        blank=True,
        null=True,
    )

    dl_url = models.URLField(
        verbose_name='Télécharger depuis une URL',
        max_length=2000,
        blank=True,
        null=True,
    )

    up_file = models.FileField(
        verbose_name='Téléverser un ou plusieurs fichiers',
        blank=True,
        null=True,
        upload_to=_up_file_upload_to,
        max_length=255,
    )

    LANG_CHOICES = (
        ('french', 'Français'),
        ('english', 'Anglais'),
        ('italian', 'Italien'),
        ('german', 'Allemand'),
        ('other', 'Autre'),
    )

    lang = models.CharField(
        verbose_name='Langue',
        choices=LANG_CHOICES,
        default='french',
        max_length=10,
    )

    format_type = models.ForeignKey(
        to='ResourceFormats',
        verbose_name='Format',
        blank=False,
        null=True,
    )

    LEVEL_CHOICES = (
        ('public', 'Tous les utilisateurs'),
        ('registered', 'Utilisateurs authentifiés'),
        ('only_allowed_users',
         'Utilisateurs authentifiés avec droits spécifiques'),
        ('same_organization', 'Utilisateurs de cette organisation uniquement'),
        ('any_organization', 'Organisations spécifiées'),
        ('only_idgo_partners', 'Tous les %s' % IDGO_USER_PARTNER_LABEL_PLURAL),
    )

    restricted_level = models.CharField(
        verbose_name="Restriction d'accès",
        choices=LEVEL_CHOICES,
        default='public',
        max_length=20,
        blank=True,
        null=True,
    )

    profiles_allowed = models.ManyToManyField(
        to='Profile',
        verbose_name='Utilisateurs autorisés',
        blank=True,
    )

    organisations_allowed = models.ManyToManyField(
        to='Organisation',
        verbose_name='Organisations autorisées',
        blank=True,
    )

    dataset = models.ForeignKey(
        to='Dataset',
        verbose_name='Jeu de données',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

    bbox = models.PolygonField(
        verbose_name='Rectangle englobant',
        blank=True,
        null=True,
        srid=4171,
    )

    geo_restriction = models.BooleanField(
        verbose_name='Restriction géographique',
        default=False,
    )

    extractable = models.BooleanField(
        verbose_name='Extractible',
        default=True,
    )

    ogc_services = models.BooleanField(
        verbose_name='Services OGC',
        default=True,
    )

    created_on = models.DateTimeField(
        verbose_name='Date de création de la resource',
        blank=True,
        null=True,
        default=timezone.now,
    )

    last_update = models.DateTimeField(
        verbose_name='Date de dernière modification de la resource',
        blank=True,
        null=True,
    )

    TYPE_CHOICES = (
        ('raw', 'Données brutes'),
        ('annexe', 'Documentation associée'),
        ('service', 'Service'),
    )

    data_type = models.CharField(
        verbose_name='Type de la ressource',
        choices=TYPE_CHOICES,
        max_length=10,
        default='raw',
    )

    synchronisation = models.BooleanField(
        verbose_name='Synchronisation de données distante',
        default=False,
    )

    EXTRA_FREQUENCY_CHOICES = (
        # ('5mn', 'Toutes les 5 minutes'),
        # ('15mn', 'Toutes les 15 minutes'),
        # ('20mn', 'Toutes les 20 minutes'),
        # ('30mn', 'Toutes les 30 minutes'),
    )

    FREQUENCY_CHOICES = (
        ('1hour', 'Toutes les heures'),
        ('3hours', 'Toutes les trois heures'),
        ('6hours', 'Toutes les six heures'),
        ('daily', 'Quotidienne (tous les jours à minuit)'),
        ('weekly', 'Hebdomadaire (tous les lundi)'),
        ('bimonthly', 'Bimensuelle (1er et 15 de chaque mois)'),
        ('monthly', 'Mensuelle (1er de chaque mois)'),
        ('quarterly',
         'Trimestrielle (1er des mois de janvier, avril, juillet, octobre)'),
        ('biannual', 'Semestrielle (1er janvier et 1er juillet)'),
        ('annual', 'Annuelle (1er janvier)'),
        ('never', 'Jamais'),
    )

    sync_frequency = models.CharField(
        verbose_name='Fréquence de synchronisation',
        max_length=20,
        blank=True,
        null=True,
        choices=FREQUENCY_CHOICES + EXTRA_FREQUENCY_CHOICES,
        default='never',
    )

    crs = models.ForeignKey(
        to='SupportedCrs',
        verbose_name='CRS',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

    def __str__(self):
        return self.title

    # Propriétés
    # ==========

    _encoding = 'utf-8'

    @property
    def encoding(self):
        return self._encoding

    @encoding.setter
    def encoding(self, value):
        if value:
            self._encoding = value

    @property
    def filename(self):
        if self.ftp_file:
            return self.ftp_file.name
        if self.up_file:
            return self.up_file.name
        return '{}.{}'.format(slugify(self.title), self.format.lower())

    @property
    def ckan_url(self):
        return urljoin(
            CKAN_URL, 'dataset/{}/resource/{}/'.format(self.dataset.slug,
                                                       self.ckan_id))

    @property
    def api_location(self):
        kwargs = {
            'dataset_name': self.dataset.slug,
            'resource_id': self.ckan_id
        }
        return reverse('api:resource_show', kwargs=kwargs)

    @property
    def title_overflow(self):
        return three_suspension_points(self.title)

    @property
    def anonymous_access(self):
        return self.restricted_level == 'public'

    @property
    def is_datagis(self):
        return self.get_layers() and True or False

    # Méthodes héritées
    # =================

    def save(self,
             *args,
             current_user=None,
             synchronize=False,
             file_extras=None,
             skip_download=False,
             update_m2m=False,
             update_dataset=True,
             **kwargs):

        if update_m2m:
            return super().save(*args, **kwargs)

        if 'update_fields' in kwargs:
            return super().save(*args, **kwargs)

        # Version précédante de la ressource (avant modification)
        previous, created = self.pk \
            and (Resource.objects.get(pk=self.pk), False) or (None, True)

        if previous:
            # crs est immuable sauf si le jeu de données change (Cf. plus bas)
            self.crs = previous.crs

        # Quelques valeur par défaut à la création de l'instance
        if created:
            self.geo_restriction = False
            self.ogc_services = True
            self.extractable = True
        # La restriction au territoire de compétence désactive toujours les services OGC
        if self.geo_restriction:
            self.ogc_services = False

        self.last_update = timezone.now()

        if created:
            super().save(*args, **kwargs)
            kwargs['force_insert'] = False

        # Quelques contrôles sur les fichiers de données téléversée ou à télécharger
        filename = False
        content_type = None
        file_must_be_deleted = False  # permet d'indiquer si les fichiers doivent être supprimés à la fin de la chaine de traitement
        publish_raw_resource = True  # permet d'indiquer si les ressources brutes sont publiées dans CKAN

        if self.ftp_file and not skip_download:
            filename = self.ftp_file.file.name
            # Si la taille de fichier dépasse la limite autorisée,
            # on traite les données en fonction du type détecté
            if self.ftp_file.size > DATA_TRANSMISSION_SIZE_LIMITATION:
                logger.info("This is a big file: %s." % self.ftp_file.size)

                publish_raw_resource = False  # IMPORTANT

                s0 = str(self.ckan_id)
                s1, s2, s3 = s0[:3], s0[3:6], s0[6:]
                dir = os.path.join(CKAN_STORAGE_PATH, s1, s2)
                os.makedirs(dir, mode=0o777, exist_ok=True)

                logger.info("cp %s %s" % (filename, os.path.join(dir, s3)))
                shutil.copyfile(filename, os.path.join(dir, s3))

                src = os.path.join(dir, s3)
                dst = os.path.join(dir, filename.split('/')[-1])
                logger.info("ln -s %s %s" % (dst, src))
                try:
                    os.symlink(src, dst)
                except (FileNotFoundError, FileExistsError) as e:
                    logger.exception(e)
                    logger.warning("Error was ignored.")
                    pass

        elif (self.up_file and file_extras):
            # GDAL/OGR ne semble pas prendre de fichier en mémoire..
            # ..à vérifier mais si c'est possible comment indiquer le vsi en préfixe du filename ?
            filename = self.up_file.path
            self.save(update_fields=('up_file', ))
            file_must_be_deleted = True

        elif self.dl_url and not skip_download:
            try:
                directory, filename, content_type = download(
                    self.dl_url,
                    settings.MEDIA_ROOT,
                    max_size=DOWNLOAD_SIZE_LIMIT)
            except SizeLimitExceededError as e:
                logger.exception(e)
                l = len(str(e.max_size))
                if l > 6:
                    m = '{0} mo'.format(Decimal(int(e.max_size) / 1024 / 1024))
                elif l > 3:
                    m = '{0} ko'.format(Decimal(int(e.max_size) / 1024))
                else:
                    m = '{0} octets'.format(int(e.max_size))
                raise ValidationError(('La taille du fichier dépasse '
                                       'la limite autorisée : {0}.').format(m),
                                      code='dl_url')
            except Exception as e:
                logger.exception(e)
                if e.__class__.__name__ == 'HTTPError':
                    if e.response.status_code == 404:
                        msg = ('La ressource distante ne semble pas exister. '
                               "Assurez-vous que l'URL soit correcte.")
                    if e.response.status_code == 403:
                        msg = ("Vous n'avez pas l'autorisation pour "
                               'accéder à la ressource.')
                    if e.response.status_code == 401:
                        msg = ('Une authentification est nécessaire '
                               'pour accéder à la ressource.')
                else:
                    msg = 'Le téléchargement du fichier a échoué.'
                raise ValidationError(msg, code='dl_url')
            file_must_be_deleted = True

        # Synchronisation avec CKAN
        # =========================

        # La synchronisation doit s'effectuer avant la publication des
        # éventuelles couches de données SIG car dans le cas des données
        # de type « raster », nous utilisons le filestore de CKAN.
        if synchronize:
            if publish_raw_resource:
                self.synchronize(content_type=content_type,
                                 file_extras=file_extras,
                                 filename=filename,
                                 with_user=current_user)
            else:
                url = reduce(urljoin, [
                    CKAN_URL, 'dataset/',
                    str(self.dataset.ckan_id) + '/', 'resource/',
                    str(self.ckan_id) + '/', 'download/',
                    Path(self.ftp_file.name).name
                ])
                self.synchronize(url=url, with_user=current_user)

        # Détection des données SIG
        # =========================
        if filename:
            # On vérifie s'il s'agit de données SIG, uniquement pour
            # les extensions de fichier autorisées..
            extension = self.format_type.extension.lower()
            if self.format_type.is_gis_format:
                # Si c'est le cas, on monte les données dans la base PostGIS dédiée
                # et on déclare la couche au service OGC:WxS de l'organisation.

                # Mais d'abord, on vérifie si la ressource contient
                # déjà des « Layers », auquel cas il faudra vérifier si
                # la table de données a changée.
                existing_layers = {}
                if not created:
                    existing_layers = dict(
                        (re.sub('^(\w+)_[a-z0-9]{7}$', '\g<1>', layer.name),
                         layer.name) for layer in self.get_layers())

                try:

                    # C'est carrément moche mais c'est pour aller vite.
                    # Il faudrait factoriser tout ce bazar et créer
                    # un décorateur pour gérer le rool-back sur CKAN.

                    try:
                        gdalogr_obj = get_gdalogr_object(filename, extension)
                    except NotDataGISError:
                        tables = []
                        pass
                    else:

                        try:
                            self.format_type = ResourceFormats.objects.get(
                                extension=extension,
                                ckan_format=gdalogr_obj.format)
                        # except ResourceFormats.MultipleObjectsReturned:
                        #     pass
                        except Exception as e:
                            logger.exception(e)
                            logger.warning("Error was ignored.")
                            pass

                        # ==========================
                        # Jeu de données vectorielle
                        # ==========================

                        if gdalogr_obj.__class__.__name__ == 'OgrOpener':

                            # On convertit les données vers PostGIS

                            try:
                                tables = ogr2postgis(
                                    gdalogr_obj,
                                    update=existing_layers,
                                    epsg=self.crs and self.crs.auth_code
                                    or None,
                                    encoding=self.encoding)

                            except NotOGRError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                msg = (
                                    "Le fichier reçu n'est pas reconnu "
                                    'comme étant un jeu de données SIG correct.'
                                )
                                raise ValidationError(msg, code='__all__')

                            except DataDecodingError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                msg = (
                                    'Impossible de décoder correctement les '
                                    "données. Merci d'indiquer l'encodage "
                                    'ci-dessous.')
                                raise ValidationError(msg, code='encoding')

                            except WrongDataError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                msg = (
                                    'Votre ressource contient des données SIG que '
                                    'nous ne parvenons pas à lire correctement. '
                                    'Un ou plusieurs objets sont erronés.')
                                raise ValidationError(msg)

                            except NotFoundSrsError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                msg = (
                                    'Votre ressource semble contenir des données SIG '
                                    'mais nous ne parvenons pas à détecter le système '
                                    'de coordonnées. Merci de sélectionner le code du '
                                    'CRS dans la liste ci-dessous.')
                                raise ValidationError(msg, code='crs')

                            except NotSupportedSrsError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                msg = (
                                    'Votre ressource semble contenir des données SIG '
                                    'mais le système de coordonnées de celles-ci '
                                    "n'est pas supporté par l'application.")
                                raise ValidationError(msg, code='__all__')

                            except ExceedsMaximumLayerNumberFixedError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                raise ValidationError(e.__str__(),
                                                      code='__all__')

                            else:
                                # Ensuite, pour tous les jeux de données SIG trouvés,
                                # on crée le service ows à travers la création de `Layer`
                                try:
                                    Layer = apps.get_model(
                                        app_label='idgo_admin',
                                        model_name='Layer')
                                    for table in tables:
                                        try:
                                            Layer.objects.get(name=table['id'],
                                                              resource=self)
                                        except Layer.DoesNotExist:
                                            save_opts = {
                                                'synchronize': synchronize
                                            }
                                            bbox = transform(
                                                table['bbox'], table['epsg'])
                                            Layer.vector.create(
                                                bbox=bbox,
                                                name=table['id'],
                                                resource=self,
                                                save_opts=save_opts)
                                except Exception as e:
                                    logger.exception(e)
                                    file_must_be_deleted and remove_file(
                                        filename)
                                    for table in tables:
                                        drop_table(table['id'])
                                    raise e

                        # ==========================
                        # Jeu de données matricielle
                        # ==========================

                        if gdalogr_obj.__class__.__name__ == 'GdalOpener':

                            coverage = gdalogr_obj.get_coverage()

                            try:
                                tables = [
                                    gdalinfo(
                                        coverage,
                                        update=existing_layers,
                                        epsg=self.crs and self.crs.auth_code
                                        or None)
                                ]

                            except NotFoundSrsError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                msg = (
                                    'Votre ressource semble contenir des données SIG '
                                    'mais nous ne parvenons pas à détecter le système '
                                    'de coordonnées. Merci de sélectionner le code du '
                                    'CRS dans la liste ci-dessous.')
                                raise ValidationError(msg, code='crs')

                            except NotSupportedSrsError as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                msg = (
                                    'Votre ressource semble contenir des données SIG '
                                    'mais le système de coordonnées de celles-ci '
                                    "n'est pas supporté par l'application.")
                                raise ValidationError(msg, code='__all__')

                            # Super Crado Code
                            s0 = str(self.ckan_id)
                            s1, s2, s3 = s0[:3], s0[3:6], s0[6:]
                            dir = os.path.join(CKAN_STORAGE_PATH, s1, s2)
                            src = os.path.join(dir, s3)
                            dst = os.path.join(dir, filename.split('/')[-1])
                            try:
                                os.symlink(src, dst)
                            except FileExistsError as e:
                                logger.exception(e)
                            except FileNotFoundError as e:
                                logger.exception(e)
                            else:
                                logger.debug(
                                    'Created a symbolic link {dst} pointing to {src}.'
                                    .format(dst=dst, src=src))

                            try:
                                Layer = apps.get_model(app_label='idgo_admin',
                                                       model_name='Layer')
                                for table in tables:
                                    try:
                                        Layer.objects.get(name=table['id'],
                                                          resource=self)
                                    except Layer.DoesNotExist:
                                        Layer.raster.create(bbox=table['bbox'],
                                                            name=table['id'],
                                                            resource=self)
                            except Exception as e:
                                logger.exception(e)
                                file_must_be_deleted and remove_file(filename)
                                raise e

                except Exception as e:
                    logger.exception(e)
                    if created:
                        if current_user:
                            username = current_user.username
                            apikey = CkanHandler.get_user(username)['apikey']
                            with CkanUserHandler(apikey) as ckan:
                                ckan.delete_resource(str(self.ckan_id))
                        else:
                            CkanHandler.delete_resource(str(self.ckan_id))
                        for layer in self.get_layers():
                            layer.delete(current_user=current_user)
                    # Puis on « raise » l'erreur
                    raise e

                # On met à jour les champs de la ressource
                SupportedCrs = apps.get_model(app_label='idgo_admin',
                                              model_name='SupportedCrs')
                crs = [
                    SupportedCrs.objects.get(auth_name='EPSG',
                                             auth_code=table['epsg'])
                    for table in tables
                ]
                # On prend la première valeur (c'est moche)
                self.crs = crs and crs[0] or None

                # Si les données changent..
                if existing_layers and \
                        set(previous.get_layers()) != set(self.get_layers()):
                    # on supprime les anciens `layers`..
                    for layer in previous.get_layers():
                        layer.delete()
        ####
        if self.get_layers():
            extent = self.get_layers().aggregate(
                models.Extent('bbox')).get('bbox__extent')
            if extent:
                xmin, ymin = extent[0], extent[1]
                xmax, ymax = extent[2], extent[3]
                setattr(self, 'bbox', bounds_to_wkt(xmin, ymin, xmax, ymax))
        else:
            # Si la ressource n'est pas de type SIG, on passe les trois arguments
            # qui concernent exclusivement ces dernières à « False ».
            self.geo_restriction = False
            self.ogc_services = False
            self.extractable = False

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

        # Puis dans tous les cas..
        # on met à jour le statut des couches du service cartographique..
        if not created:
            self.update_enable_layers_status()

        # on supprime les données téléversées ou téléchargées..
        if file_must_be_deleted:
            remove_file(filename)

        # [Crado] on met à jour la ressource CKAN
        if synchronize:
            try:
                CkanHandler.update_resource(str(self.ckan_id),
                                            extracting_service=str(
                                                self.extractable))
            except Exception as e:
                logger.exception(e)
                logger.warning("Error was ignored.")

        for layer in self.get_layers():
            layer.save(synchronize=synchronize)

        if update_dataset:
            self.dataset.date_modification = timezone.now().date()
            self.dataset.save(current_user=None,
                              synchronize=True,
                              update_fields=['date_modification'])

    def delete(self,
               *args,
               current_user=None,
               synchronize_dataset=True,
               **kwargs):
        with_user = current_user

        for layer in self.get_layers():
            layer.delete(current_user=current_user)

        # On supprime la ressource CKAN
        ckan_id = str(self.ckan_id)
        if with_user:
            username = with_user.username

            apikey = CkanHandler.get_user(username)['apikey']
            with CkanUserHandler(apikey=apikey) as ckan_user:
                ckan_user.delete_resource(ckan_id)
        else:
            CkanHandler.delete_resource(ckan_id)

        # On supprime l'instance
        super().delete(*args, **kwargs)

        # Ce n'est vraiment pas une bonne idée de synchroniser ici le dataset :
        self.dataset.date_modification = timezone.now().date()
        self.dataset.save(current_user=current_user,
                          synchronize=synchronize_dataset,
                          update_fields=['date_modification'])

    # Autres méthodes
    # ===============

    def synchronize(self,
                    url=None,
                    filename=None,
                    content_type=None,
                    file_extras=None,
                    with_user=None):
        """Synchronizer le jeu de données avec l'instance de CKAN."""
        # Identifiant de la resource CKAN :
        id = str(self.ckan_id)

        ckan_resource = {}
        try:
            ckan_resource = CkanHandler.get_resource(id)
        except Exception as e:
            logger.warning(e)

        # Définition des propriétés du « package » :
        data = {
            'crs': self.crs and self.crs.description or '',
            'name': self.title,
            'description': self.description,
            'data_type': self.data_type,
            'extracting_service': str(self.extractable or False),  # I <3 CKAN
            'format': self.format_type and self.format_type.ckan_format,
            'view_type': self.format_type and self.format_type.ckan_view,
            'id': id,
            'lang': self.lang,
            'restricted_by_jurisdiction': str(self.geo_restriction),
            'url': url and url or '',
            'api': ckan_resource.get('api', '{}'),
        }

        # (0) Aucune restriction
        if self.restricted_level == 'public':
            restricted = json.dumps({'level': 'public'})

        # (1) Uniquement pour un utilisateur connecté
        elif self.restricted_level == 'registered':
            restricted = json.dumps({'level': 'registered'})

        # (2) Seulement les utilisateurs indiquées
        elif self.restricted_level == 'only_allowed_users':
            restricted = json.dumps({
                'allowed_users':
                ','.join(
                    self.profiles_allowed.exists()
                    and [p.user.username
                         for p in self.profiles_allowed.all()] or []),
                'level':
                'only_allowed_users'
            })

        # (3) Les utilisateurs de cette organisation
        elif self.restricted_level == 'same_organization':
            restricted = json.dumps({
                'allowed_users':
                ','.join(
                    get_all_users_for_organisations(
                        self.organisations_allowed.all())),
                'level':
                'only_allowed_users'
            })

        # (3) Les utilisateurs des organisations indiquées
        elif self.restricted_level == 'any_organization':
            restricted = json.dumps({
                'allowed_users':
                ','.join(
                    get_all_users_for_organisations(
                        self.organisations_allowed.all())),
                'level':
                'only_allowed_users'
            })

        # (4) Les utilisateurs partenaires IDGO
        elif self.restricted_level == 'only_idgo_partners':
            restricted = json.dumps({
                'allowed_groups': ['idgo-partner'],
                'level': 'only_group_member'
            })

        data['restricted'] = restricted

        if self.referenced_url:
            data['url'] = self.referenced_url

        if self.dl_url and filename:
            downloaded_file = File(open(filename, 'rb'))
            data['upload'] = downloaded_file
            data['size'] = downloaded_file.size
            data['mimetype'] = content_type

        if self.up_file and file_extras:
            data['upload'] = self.up_file.file
            data['size'] = file_extras.get('size')
            data['mimetype'] = file_extras.get('mimetype')

        if self.ftp_file:
            if not url and filename:
                data['upload'] = self.ftp_file.file
                data['size'] = self.ftp_file.size
            if url or filename:
                if self.format_type and (
                        type(self.format_type.mimetype) is list
                        and len(self.format_type.mimetype)):
                    data['mimetype'] = self.format_type.mimetype[0]
                else:
                    data['mimetype'] = 'text/plain'

            # data['force_url_type'] = 'upload'  # NON PREVU PAR CKAN API

        if self.data_type == 'raw':
            if self.ftp_file or self.dl_url or self.up_file:
                data['resource_type'] = 'file.upload'
            elif self.referenced_url:
                data['resource_type'] = 'file'
        if self.data_type == 'annexe':
            data['resource_type'] = 'documentation'
        if self.data_type == 'service':
            data['resource_type'] = 'api'

        ckan_package = CkanHandler.get_package(str(self.dataset.ckan_id))

        if with_user:
            username = with_user.username

            apikey = CkanHandler.get_user(username)['apikey']
            with CkanUserHandler(apikey=apikey) as ckan:
                ckan.publish_resource(ckan_package, **data)
        else:
            return CkanHandler.publish_resource(ckan_package, **data)

    def get_layers(self, **kwargs):
        Layer = apps.get_model(app_label='idgo_admin', model_name='Layer')
        return Layer.objects.filter(resource=self, **kwargs)

    def update_enable_layers_status(self):
        for layer in self.get_layers():
            layer.handle_enable_ows_status()

    def is_profile_authorized(self, user):
        Profile = apps.get_model(app_label='idgo_admin', model_name='Profile')
        if not user.pk:
            raise IntegrityError("User does not exists.")

        if self.restricted_level == 'only_allowed_users' and self.profiles_allowed.exists(
        ):
            return user in [p.user for p in self.profiles_allowed.all()]
        elif self.restricted_level in (
                'same_organization',
                'any_organization') and self.organisations_allowed.exists():
            return user in [
                p.user for p in Profile.objects.filter(
                    organisation__in=self.organisations_allowed.all(),
                    organisation__is_active=True)
            ]
        elif self.restricted_level == 'only_idgo_partners':
            return user in [
                p.user for p in Profile.objects.filter(crige_membership=True)
            ]

        return True
Ejemplo n.º 20
0
class Erp(models.Model):
    HISTORY_MAX_LATEST_ITEMS = 25  # Fix me : move to settings

    SOURCE_ACCESLIBRE = "acceslibre"
    SOURCE_ADMIN = "admin"
    SOURCE_API = "api"
    SOURCE_API_ENTREPRISE = "entreprise_api"
    SOURCE_CCONFORME = "cconforme"
    SOURCE_GENDARMERIE = "gendarmerie"
    SOURCE_NESTENN = "nestenn"
    SOURCE_ODS = "opendatasoft"
    SOURCE_PUBLIC = "public"
    SOURCE_PUBLIC_ERP = "public_erp"
    SOURCE_SERVICE_PUBLIC = "service_public"
    SOURCE_SIRENE = "sirene"
    SOURCE_TH = "tourisme-handicap"
    SOURCE_VACCINATION = "centres-vaccination"
    SOURCE_CHOICES = (
        (SOURCE_ACCESLIBRE, "Base de données Acceslibre"),
        (SOURCE_ADMIN, "Back-office"),
        (SOURCE_API, "API"),
        (SOURCE_API_ENTREPRISE, "API Entreprise (publique)"),
        (SOURCE_CCONFORME, "cconforme"),
        (SOURCE_GENDARMERIE, "Gendarmerie"),
        (SOURCE_NESTENN, "Nestenn"),
        (SOURCE_ODS, "API OpenDataSoft"),
        (SOURCE_PUBLIC, "Saisie manuelle publique"),
        (SOURCE_PUBLIC_ERP, "API des établissements publics"),
        (SOURCE_SERVICE_PUBLIC, "Service Public"),
        (SOURCE_SIRENE, "API Sirene INSEE"),
        (SOURCE_TH, "Tourisme & Handicap"),
        (SOURCE_VACCINATION, "Centres de vaccination"),
    )
    USER_ROLE_ADMIN = "admin"
    USER_ROLE_GESTIONNAIRE = "gestionnaire"
    USER_ROLE_PUBLIC = "public"
    USER_ROLE_SYSTEM = "system"
    USER_ROLES = (
        (USER_ROLE_ADMIN, "Administration"),
        (USER_ROLE_GESTIONNAIRE, "Gestionnaire"),
        (USER_ROLE_PUBLIC, "Utilisateur public"),
        (USER_ROLE_SYSTEM, "Système"),
    )

    class Meta:
        ordering = ("nom", )
        verbose_name = "Établissement"
        verbose_name_plural = "Établissements"
        indexes = [
            models.Index(fields=["source", "source_id"]),
            models.Index(fields=["slug"]),
            models.Index(fields=["commune"]),
            models.Index(fields=["commune", "activite_id"]),
            models.Index(fields=["user_type"]),
            GinIndex(name="nom_trgm",
                     fields=["nom"],
                     opclasses=["gin_trgm_ops"]),
            GinIndex(fields=["search_vector"]),
            GinIndex(fields=["metadata"], name="gin_metadata"),
        ]

    objects = managers.ErpQuerySet.as_manager()

    uuid = models.UUIDField(default=uuid.uuid4, unique=True)

    source = models.CharField(
        max_length=100,
        null=True,
        verbose_name="Source",
        default=SOURCE_PUBLIC,
        choices=SOURCE_CHOICES,
        help_text="Nom de la source de données dont est issu cet ERP",
    )
    source_id = models.CharField(
        max_length=255,
        null=True,
        verbose_name="Source ID",
        help_text="Identifiant de l'ERP dans la source initiale de données",
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        verbose_name="Contributeur",
        on_delete=models.SET_NULL,
    )
    user_type = models.CharField(
        max_length=50,
        choices=USER_ROLES,
        verbose_name="Profil de contributeur",
        default=USER_ROLE_SYSTEM,
    )

    commune_ext = models.ForeignKey(
        Commune,
        null=True,
        blank=True,
        verbose_name="Commune (relation)",
        help_text="La commune de cet établissement",
        on_delete=models.SET_NULL,
    )
    nom = models.CharField(max_length=255,
                           help_text="Nom de l'établissement ou de l'enseigne")
    slug = AutoSlugField(
        default="",
        unique=True,
        populate_from="nom",
        help_text="Identifiant d'URL (slug)",
        max_length=255,
    )
    activite = models.ForeignKey(
        Activite,
        null=True,
        blank=True,
        verbose_name="Activité",
        help_text=
        "Domaine d'activité de l'ERP. Attention, la recherche se fait sur les lettres accentuées",
        on_delete=models.SET_NULL,
    )
    published = models.BooleanField(
        default=True,
        verbose_name="Publié",
        help_text=
        "Statut de publication de cet ERP: si la case est décochée, l'ERP ne sera pas listé publiquement",
    )
    geom = models.PointField(
        null=True,
        blank=True,
        verbose_name="Localisation",
        help_text=
        "Géolocalisation (carte rafraîchie une fois l'enregistrement sauvegardé)",
    )
    siret = models.CharField(
        max_length=14,
        null=True,
        blank=True,
        verbose_name="SIRET",
        help_text="Numéro SIRET si l'ERP est une entreprise",
    )
    # contact
    telephone = models.CharField(
        max_length=20,
        null=True,
        blank=True,
        verbose_name="Téléphone",
        help_text="Numéro de téléphone de l'ERP",
    )
    site_internet = models.URLField(
        max_length=255,
        null=True,
        blank=True,
        help_text="Adresse du site internet de l'ERP",
    )
    contact_email = models.EmailField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name="Courriel",
        help_text="Adresse email permettant de contacter l'ERP",
    )
    contact_url = models.URLField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name="Lien vers outil de contact",
        help_text=
        "Lien hypertexte permettant de contacter l'établissement (formulaire, chatbot, etc.)",
    )
    # adresse
    numero = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name="Numéro",
        help_text=
        "Numéro dans la voie, incluant le complément (BIS, TER, etc.)",
    )
    voie = models.CharField(max_length=255,
                            null=True,
                            blank=True,
                            help_text="Voie")
    lieu_dit = models.CharField(max_length=255,
                                null=True,
                                blank=True,
                                help_text="Lieu dit")
    code_postal = models.CharField(max_length=5, help_text="Code postal")
    commune = models.CharField(max_length=255, help_text="Nom de la commune")
    code_insee = models.CharField(
        max_length=5,
        null=True,
        blank=True,
        verbose_name="Code INSEE",
        help_text="Code INSEE de la commune",
    )

    # Metadata
    # Notes:
    # - DO NOT store Python datetimes or attempt to pass some; JSON doesn't
    # have a native datetime type, so while we could encode dates, we couldn't
    # reliably decode them
    # - For updating nested values, you have to retrieve the whole object,
    # update the target nested values so the metadata object is mutated, then
    # save the instance. See tests for illustration.
    # XXX: we might want to provide convenient getter and setters targetting
    # given nested keys later at some point.
    metadata = models.JSONField(default=dict)

    # datetimes
    created_at = models.DateTimeField(auto_now_add=True,
                                      verbose_name="Date de création")
    updated_at = models.DateTimeField(auto_now=True,
                                      verbose_name="Dernière modification")

    # search vector
    search_vector = SearchVectorField("Search vector", null=True)

    def __str__(self):
        return f"ERP #{self.id} ({self.nom}, {self.commune})"

    def get_activite_icon(self):
        default = "amenity_public_building"
        if self.activite and self.activite.icon:
            return self.activite.icon
        return default

    def get_activite_vector_icon(self):
        default = "building"
        if self.activite and self.activite.vector_icon:
            return self.activite.vector_icon
        return default

    def get_history(self, exclude_changes_from=None):
        "Combines erp and related accessibilite histories."
        erp_history = _get_history(
            self.get_versions(),
            exclude_fields=(
                "uuid",
                "source",
                "source_id",
                "search_vector",
            ),
            exclude_changes_from=exclude_changes_from,
        )
        accessibilite_history = self.accessibilite.get_history(
            exclude_changes_from=exclude_changes_from)
        global_history = erp_history + accessibilite_history
        global_history.sort(key=lambda x: x["date"], reverse=True)
        return global_history

    def get_versions(self):
        # take the last n revisions
        qs = (Version.objects.get_for_object(self).select_related(
            "revision__user").order_by("-revision__date_created")
              [:self.HISTORY_MAX_LATEST_ITEMS + 1])

        # make it a list, so it's reversable
        versions = list(qs)
        # reorder the slice by date_created ASC
        versions.reverse()
        return versions

    def editable_by(self, user):
        if not user.is_active:
            return False
        # admins can do whatever they want
        if user.is_superuser:
            return True
        # intrapreneurs can update any erps
        if "intrapreneurs" in list(user.groups.values_list("name")):
            return True
        # users can take over erps with no owner
        if not self.user:
            return True
        # check ownership
        if user.id != self.user.id:
            return False
        return True

    def get_absolute_uri(self):
        return f"{settings.SITE_ROOT_URL}{self.get_absolute_url()}"

    def get_absolute_url(self):
        if self.commune_ext:
            commune_slug = self.commune_ext.slug
        else:
            commune_slug = slugify(f"{self.departement}-{self.commune}")
        if self.activite is None:
            return reverse(
                "commune_erp",
                kwargs=dict(commune=commune_slug, erp_slug=self.slug),
            )
        else:
            return reverse(
                "commune_activite_erp",
                kwargs=dict(
                    commune=commune_slug,
                    activite_slug=self.activite.slug,
                    erp_slug=self.slug,
                ),
            )

    def get_admin_url(self):
        return (reverse("admin:erp_erp_change", kwargs={"object_id": self.pk})
                if self.pk else None)

    def get_global_timestamps(self):
        (created_at, updated_at) = (self.created_at, self.updated_at)
        if self.has_accessibilite():
            (a_created_at, a_updated_at) = (
                self.accessibilite.created_at,
                self.accessibilite.updated_at,
            )
            (created_at, updated_at) = (
                a_created_at if a_created_at > created_at else created_at,
                a_updated_at if a_updated_at > updated_at else updated_at,
            )
        return {
            "created_at": created_at,
            "updated_at": updated_at,
        }

    def has_accessibilite(self):
        return hasattr(self,
                       "accessibilite") and self.accessibilite is not None

    def is_online(self):
        return self.published and self.has_accessibilite(
        ) and self.geom is not None

    def is_subscribed_by(self, user):
        return ErpSubscription.objects.filter(user=user, erp=self).count() == 1

    @property
    def adresse(self):
        pieces = filter(
            lambda x: x is not None,
            [
                self.numero,
                self.voie,
                self.lieu_dit,
                self.code_postal,
                self.commune_ext.nom if self.commune_ext else self.commune,
            ],
        )
        return " ".join(pieces).strip().replace("  ", " ")

    @property
    def short_adresse(self):
        pieces = filter(
            lambda x: x is not None,
            [
                self.numero,
                self.voie,
                self.lieu_dit,
            ],
        )
        return " ".join(pieces).strip().replace("  ", " ")

    @property
    def departement(self):
        return self.code_postal[:2]

    @classmethod
    def update_coordinates(cls):
        counter = 0
        erp_updates = 0
        for e in cls.objects.filter(commune_ext__isnull=False):
            if not e.commune_ext.in_contour(Point(e.geom.x, e.geom.y)):
                print(f"Erp concerné : {e.nom}; {e.code_postal}; {e.commune}")
                counter += 1
                try:
                    coordinates = geocoder.geocode(
                        e.short_adresse, citycode=e.commune_ext.code_insee)
                except Exception as error:
                    print(error)
                else:
                    if coordinates:
                        e.geom = Point(coordinates["geom"][0],
                                       coordinates["geom"][1])
                        e.save()
                        erp_updates += 1
                    else:
                        print("No Coordinates")
        print(f"{erp_updates} erps mis à jour sur {counter}")

    @classmethod
    def fix_import_service_public(cls):
        qs = cls.objects.filter(numero__isnull=False, voie__isnull=False)
        for erp in qs:
            if all(not char.isdigit() for char in erp.numero):
                erp.voie = f"{erp.numero} {erp.voie}"
                erp.numero = None
                erp.save()

    @classmethod
    def export_doublons(cls):
        filename = "doublons.csv"
        start_date = datetime.date(2022, 1, 19)

        qs = cls.objects.filter(accessibilite__isnull=False)
        if os.path.exists(filename):
            os.remove(filename)
        csv = open(filename, "w")
        doublons = list(e["erp_list"] for e in qs.annotate(
            voie_lower=Lower("voie"), commune_lower=Lower("commune")).values(
                "numero", "voie_lower", "code_postal", "commune_lower"
            ).annotate(erp_count=Count("pk"), erp_list=ArrayAgg(
                "pk")).order_by("-erp_count").filter(erp_count__gt=1))
        csv.write(
            f"created_at;nom;numero;voie;code_postal;commune;activite;{Accessibilite.export_data_comma_headers()}\n"
        )
        counter_doublons = 0
        for e in doublons:
            if any(erp.created_at.date() >= start_date
                   for erp in cls.objects.filter(pk__in=e)):
                for id in e:
                    counter_doublons += 1
                    erp = cls.objects.get(pk=id)
                    csv.write(
                        f"{erp.created_at.date()};{erp.nom};{erp.numero or ''};{erp.voie};{erp.code_postal};{erp.commune};{erp.activite};{erp.accessibilite.export_data_comma()};\n"
                    )
        csv.close()
        print(f"{counter_doublons} erps exportés dans {filename}")

    def clean(self):  # Fix me : move to form (abstract)
        # Code postal
        if self.code_postal and len(self.code_postal) != 5:
            raise ValidationError(
                {"code_postal": "Le code postal doit faire 5 caractères"})

        # Voie OU lieu-dit sont requis
        if self.voie is None and self.lieu_dit is None:
            error = "Veuillez entrer une voie ou un lieu-dit"
            raise ValidationError({"voie": error, "lieu_dit": error})

        # Commune
        if self.commune and self.code_postal:
            matches = Commune.objects.filter(
                nom__unaccent__iexact=self.commune,
                code_postaux__contains=[self.code_postal],
            )
            if len(matches) == 0:
                matches = Commune.objects.filter(
                    code_postaux__contains=[self.code_postal])
            if len(matches) == 0:
                matches = Commune.objects.filter(code_insee=self.code_insee)
            if len(matches) == 0:
                matches = Commune.objects.filter(
                    nom__unaccent__iexact=self.commune, )
            if len(matches) == 0:
                raise ValidationError({
                    "commune":
                    f"Commune {self.commune} introuvable, veuillez vérifier votre saisie."
                })
            else:
                self.commune_ext = matches[0]

        # SIRET
        if self.siret:
            siret = sirene.validate_siret(self.siret)
            if siret is None:
                raise ValidationError(
                    {"siret": "Ce numéro SIRET est invalide."})
            self.siret = siret

    def vote(self, user, action, comment=None):
        votes = Vote.objects.filter(erp=self, user=user)
        if votes.count() > 0:
            vote = votes.first()
            # check for vote cancellation
            if (action == "UP" and vote.value == 1) or (action == "DOWN"
                                                        and vote.value == -1
                                                        and not comment):
                vote.delete()
                return None
        else:
            vote = Vote(erp=self, user=user)
        vote.value = 1 if action == "UP" else -1
        vote.comment = comment if action == "DOWN" else None
        vote.save()
        return vote

    def save(self, *args, **kwargs):
        search_vector = SearchVector(
            Value(self.nom, output_field=models.TextField()),
            weight="A",
            config=FULLTEXT_CONFIG,
        )
        if self.activite is not None:
            search_vector = search_vector + SearchVector(
                Value(
                    self.activite.nom,
                    output_field=models.TextField(),
                ),
                weight="A",
                config=FULLTEXT_CONFIG,
            )
            if self.activite.mots_cles is not None:
                search_vector = search_vector + SearchVector(
                    Value(
                        " ".join(self.activite.mots_cles),
                        output_field=models.TextField(),
                    ),
                    weight="B",
                    config=FULLTEXT_CONFIG,
                )
        self.search_vector = search_vector
        super().save(*args, **kwargs)
Ejemplo n.º 21
0
class BoundarySet(models.Model):

    """
    A set of boundaries, corresponding to one or more shapefiles.
    """
    slug = models.SlugField(max_length=200, primary_key=True, editable=False,
        help_text=ugettext_lazy("The boundary set's unique identifier, used as a path component in URLs."))
    name = models.CharField(max_length=100, unique=True,
        help_text=ugettext_lazy('The plural name of the boundary set.'))
    singular = models.CharField(max_length=100,
        help_text=ugettext_lazy('A generic singular name for a boundary in the set.'))
    authority = models.CharField(max_length=256,
        help_text=ugettext_lazy('The entity responsible for publishing the data.'))
    domain = models.CharField(max_length=256,
        help_text=ugettext_lazy("The geographic area covered by the boundary set."))
    last_updated = models.DateField(
        help_text=ugettext_lazy('The most recent date on which the data was updated.'))
    source_url = models.URLField(blank=True,
        help_text=ugettext_lazy('A URL to the source of the data.'))
    notes = models.TextField(blank=True,
        help_text=ugettext_lazy('Free-form text notes, often used to describe changes that were made to the original source data.'))
    licence_url = models.URLField(blank=True,
        help_text=ugettext_lazy('A URL to the licence under which the data is made available.'))
    extent = JSONField(blank=True, null=True,
        help_text=ugettext_lazy("The set's boundaries' bounding box as a list like [xmin, ymin, xmax, ymax] in EPSG:4326."))
    start_date = models.DateField(blank=True, null=True,
        help_text=ugettext_lazy("The date from which the set's boundaries are in effect."))
    end_date = models.DateField(blank=True, null=True,
        help_text=ugettext_lazy("The date until which the set's boundaries are in effect."))
    extra = JSONField(default={},
        help_text=ugettext_lazy("Any additional metadata."))

    name_plural = property(lambda s: s.name)
    name_singular = property(lambda s: s.singular)

    api_fields = ('name_plural', 'name_singular', 'authority', 'domain', 'source_url', 'notes', 'licence_url', 'last_updated', 'extent', 'extra', 'start_date', 'end_date')
    api_fields_doc_from = {'name_plural': 'name', 'name_singular': 'singular'}

    class Meta:
        ordering = ('name',)
        verbose_name = ugettext_lazy('boundary set')
        verbose_name_plural = ugettext_lazy('boundary sets')

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        return super(BoundarySet, self).save(*args, **kwargs)

    def as_dict(self):
        r = {
            'related': {
                'boundaries_url': urlresolvers.reverse('boundaries_boundary_list', kwargs={'set_slug': self.slug}),
            },
        }
        for field in self.api_fields:
            r[field] = getattr(self, field)
            if not isinstance(r[field], (string_types, int, list, tuple, dict)) and r[field] is not None:
                r[field] = text_type(r[field])
        return r

    @staticmethod
    def get_dicts(sets):
        return [
            {
                'url': urlresolvers.reverse('boundaries_set_detail', kwargs={'slug': s.slug}),
                'related': {
                    'boundaries_url': urlresolvers.reverse('boundaries_boundary_list', kwargs={'set_slug': s.slug}),
                },
                'name': s.name,
                'domain': s.domain,
            } for s in sets
        ]

    def extend(self, extent):
        if self.extent[0] is None or extent[0] < self.extent[0]:
            self.extent[0] = extent[0]
        if self.extent[1] is None or extent[1] < self.extent[1]:
            self.extent[1] = extent[1]
        if self.extent[2] is None or extent[2] > self.extent[2]:
            self.extent[2] = extent[2]
        if self.extent[3] is None or extent[3] > self.extent[3]:
            self.extent[3] = extent[3]
Ejemplo n.º 22
0
class Accessibilite(models.Model):
    HISTORY_MAX_LATEST_ITEMS = 25

    class Meta:
        verbose_name = "Accessibilité"
        verbose_name_plural = "Accessibilité"

    erp = models.OneToOneField(
        Erp,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        verbose_name="Établissement",
        help_text="ERP",
    )

    ###################################
    # Transports en commun            #
    ###################################
    # Station de transport en commun
    transport_station_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("transport_station_presence"),
        verbose_name="Desserte par transports en commun",
    )
    transport_information = models.TextField(
        max_length=1000,
        null=True,
        blank=True,
        verbose_name="Informations transports",
    )

    ###################################
    # Stationnement                   #
    ###################################
    # Stationnement dans l'ERP
    stationnement_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("stationnement_presence"),
        verbose_name="Stationnement dans l'ERP",
    )
    stationnement_pmr = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("stationnement_pmr"),
        verbose_name="Stationnements PMR dans l'ERP",
    )

    # Stationnement à proximité
    stationnement_ext_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("stationnement_ext_presence"),
        verbose_name="Stationnement à proximité de l'ERP",
    )
    stationnement_ext_pmr = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("stationnement_ext_pmr"),
        verbose_name="Stationnements PMR à proximité de l'ERP",
    )

    ###################################
    # Espace et Cheminement extérieur #
    ###################################
    cheminement_ext_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Espace extérieur",
    )

    # Cheminement de plain-pied – oui / non / inconnu
    cheminement_ext_plain_pied = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("cheminement_ext_plain_pied"),
        verbose_name="Cheminement de plain-pied",
    )
    # Terrain meuble ou accidenté
    cheminement_ext_terrain_accidente = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("cheminement_ext_terrain_accidente"),
        verbose_name="Terrain meuble ou accidenté",
    )
    # Nombre de marches – nombre entre 0 et >10
    cheminement_ext_nombre_marches = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        verbose_name="Nombre de marches",
    )
    # Sens des marches de l'escalier
    cheminement_ext_sens_marches = models.CharField(
        max_length=20,
        null=True,
        blank=True,
        verbose_name="Sens de circulation de l'escalier",
        choices=schema.ESCALIER_SENS,
    )
    # Repérage des marches ou de l’escalier – oui / non / inconnu / sans objet
    cheminement_ext_reperage_marches = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Repérage des marches ou de l’escalier",
    )
    # Main courante - oui / non / inconnu / sans objet
    cheminement_ext_main_courante = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Main courante",
    )
    # Rampe – oui / non / inconnu / sans objet
    cheminement_ext_rampe = models.CharField(
        max_length=20,
        null=True,
        blank=True,
        choices=schema.RAMPE_CHOICES,
        verbose_name="Rampe",
    )
    # Ascenseur / élévateur : oui / non / inconnu / sans objet
    cheminement_ext_ascenseur = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("cheminement_ext_ascenseur"),
        verbose_name="Ascenseur/élévateur",
    )

    # Pente - oui / non / inconnu
    cheminement_ext_pente_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("cheminement_ext_pente_presence"),
        verbose_name="Pente présence",
    )

    # Pente - Aucune, légère, importante, inconnu
    cheminement_ext_pente_degre_difficulte = models.CharField(
        max_length=15,
        null=True,
        blank=True,
        choices=schema.PENTE_CHOICES,
        verbose_name="Difficulté de la pente",
    )

    # Pente - Aucune, légère, importante, inconnu
    cheminement_ext_pente_longueur = models.CharField(
        max_length=15,
        null=True,
        blank=True,
        choices=schema.PENTE_LENGTH_CHOICES,
        verbose_name="Longueur de la pente",
    )

    # Dévers - Aucun, léger, important, inconnu
    cheminement_ext_devers = models.CharField(
        max_length=15,
        null=True,
        blank=True,
        verbose_name="Dévers",
        choices=schema.DEVERS_CHOICES,
    )

    # Bande de guidage – oui / non / inconnu
    cheminement_ext_bande_guidage = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("cheminement_ext_bande_guidage"),
        verbose_name="Bande de guidage",
    )

    # Rétrécissement du cheminement  – oui / non / inconnu
    cheminement_ext_retrecissement = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("cheminement_ext_retrecissement"),
        verbose_name="Rétrécissement du cheminement",
    )

    ##########
    # Entrée #
    ##########
    # Entrée facilement repérable  – oui / non / inconnu
    entree_reperage = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Entrée facilement repérable",
    )

    # Présence d'une porte (oui / non)
    entree_porte_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_porte_presence"),
        verbose_name="Y a-t-il une porte ?",
    )
    # Manoeuvre de la porte (porte battante / porte coulissante / tourniquet / porte tambour / inconnu ou sans objet)
    entree_porte_manoeuvre = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        choices=schema.PORTE_MANOEUVRE_CHOICES,
        verbose_name="Manœuvre de la porte",
    )
    # Type de porte (manuelle / automatique / inconnu)
    entree_porte_type = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        choices=schema.PORTE_TYPE_CHOICES,
        verbose_name="Type de porte",
    )

    # Entrée vitrée
    entree_vitree = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_vitree"),
        verbose_name="Entrée vitrée",
    )
    entree_vitree_vitrophanie = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Vitrophanie",
    )

    # Entrée de plain-pied
    entree_plain_pied = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_plain_pied"),
        verbose_name="Entrée de plain-pied",
    )
    # Nombre de marches
    entree_marches = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        verbose_name="Marches d'escalier",
    )
    # Sens des marches de l'escalier
    entree_marches_sens = models.CharField(
        max_length=20,
        null=True,
        blank=True,
        verbose_name="Sens de circulation de l'escalier",
        choices=schema.ESCALIER_SENS,
    )
    # Repérage des marches ou de l'escalier
    entree_marches_reperage = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Repérage de l'escalier",
    )
    # Main courante
    entree_marches_main_courante = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Main courante",
    )
    # Rampe
    entree_marches_rampe = models.CharField(
        max_length=20,
        null=True,
        blank=True,
        verbose_name="Rampe",
        choices=schema.RAMPE_CHOICES,
    )
    # Système de guidage sonore  – oui / non / inconnu
    entree_balise_sonore = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_balise_sonore"),
        verbose_name="Présence d'une balise sonore",
    )
    # Dispositif d’appel
    entree_dispositif_appel = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_dispositif_appel"),
        verbose_name="Dispositif d'appel",
    )
    entree_dispositif_appel_type = ArrayField(
        models.CharField(max_length=255,
                         blank=True,
                         choices=schema.DISPOSITIFS_APPEL_CHOICES),
        verbose_name="Dispositifs d'appel disponibles",
        default=list,
        null=True,
        blank=True,
    )
    entree_aide_humaine = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_aide_humaine"),
        verbose_name="Aide humaine",
    )
    entree_ascenseur = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_ascenseur"),
        verbose_name="Ascenseur/élévateur",
    )

    # Largeur minimale
    entree_largeur_mini = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        verbose_name="Largeur minimale",
    )

    # Entrée spécifique PMR
    entree_pmr = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("entree_pmr"),
        verbose_name="Entrée spécifique PMR",
    )

    # Informations sur l’entrée spécifique
    entree_pmr_informations = models.TextField(
        max_length=500,
        null=True,
        blank=True,
        verbose_name="Infos entrée spécifique PMR",
    )

    ###########
    # Accueil #
    ###########
    # Visibilité directe de la zone d'accueil depuis l’entrée
    accueil_visibilite = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("accueil_visibilite"),
        verbose_name="Visibilité directe de la zone d'accueil depuis l'entrée",
    )

    # Personnel d’accueil
    accueil_personnels = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        choices=schema.PERSONNELS_CHOICES,
        verbose_name="Personnel d'accueil",
    )

    # Équipements pour personnes sourdes ou malentendantes
    accueil_equipements_malentendants_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices(
            "accueil_equipements_malentendants_presence"),
        verbose_name="Présence d'équipement(s) sourds/malentendants",
    )

    # Équipements pour personnes sourdes ou malentendantes
    accueil_equipements_malentendants = ArrayField(
        models.CharField(max_length=255,
                         blank=True,
                         choices=schema.EQUIPEMENT_MALENTENDANT_CHOICES),
        verbose_name="Équipement(s) sourd/malentendant",
        default=list,
        null=True,
        blank=True,
    )

    # Cheminement de plain pied entre l’entrée et l’accueil
    accueil_cheminement_plain_pied = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Cheminement de plain pied",
    )
    # Présence de marches entre l’entrée et l’accueil – nombre entre 0 et >10
    accueil_cheminement_nombre_marches = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        verbose_name="Nombre de marches",
    )
    # Sens des marches de l'escalier
    accueil_cheminement_sens_marches = models.CharField(
        max_length=20,
        null=True,
        blank=True,
        verbose_name="Sens de circulation de l'escalier",
        choices=schema.ESCALIER_SENS,
    )
    # Repérage des marches ou de l’escalier
    accueil_cheminement_reperage_marches = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices(
            "accueil_cheminement_reperage_marches"),
        verbose_name="Repérage des marches ou de l’escalier",
    )
    # Main courante
    accueil_cheminement_main_courante = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.NULLABLE_OR_NA_BOOLEAN_CHOICES,
        verbose_name="Main courante",
    )
    # Rampe – aucune / fixe / amovible / inconnu
    accueil_cheminement_rampe = models.CharField(
        max_length=20,
        null=True,
        blank=True,
        choices=schema.RAMPE_CHOICES,
        verbose_name="Rampe",
    )
    # Ascenseur / élévateur
    accueil_cheminement_ascenseur = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("accueil_cheminement_ascenseur"),
        verbose_name="Ascenseur/élévateur",
    )

    # Rétrécissement du cheminement
    accueil_retrecissement = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("accueil_retrecissement"),
        verbose_name="Rétrécissement du cheminement",
    )

    ##############
    # Sanitaires #
    ##############
    sanitaires_presence = models.BooleanField(
        null=True,
        blank=True,
        choices=schema.get_field_choices("sanitaires_presence"),
        verbose_name="Sanitaires",
    )
    sanitaires_adaptes = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        verbose_name="Nombre de sanitaires adaptés",
    )

    ##########
    # labels #
    ##########
    labels = ArrayField(
        models.CharField(max_length=255,
                         blank=True,
                         choices=schema.LABEL_CHOICES),
        verbose_name="Marques ou labels",
        default=list,
        null=True,
        blank=True,
    )
    labels_familles_handicap = ArrayField(
        models.CharField(max_length=255,
                         blank=True,
                         choices=schema.HANDICAP_CHOICES),
        verbose_name="Famille(s) de handicap concernées(s)",
        default=list,
        null=True,
        blank=True,
    )
    labels_autre = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name="Autre label",
    )

    #####################
    # Commentaire libre #
    #####################
    commentaire = models.TextField(
        max_length=1000,
        null=True,
        blank=True,
        verbose_name="Commentaire libre",
    )

    ##########################
    # Registre               #
    ##########################
    registre_url = models.URLField(
        max_length=255,
        null=True,
        blank=True,
        verbose_name="URL du registre",
    )

    ##########################
    # Conformité             #
    ##########################
    conformite = models.BooleanField(
        null=True,
        blank=True,
        verbose_name="Conformité",
        choices=schema.get_field_choices("conformite"),
    )

    # Datetimes
    created_at = models.DateTimeField(auto_now_add=True,
                                      verbose_name="Date de création")
    updated_at = models.DateTimeField(auto_now=True,
                                      verbose_name="Dernière modification")

    def __str__(self):
        if self.erp:
            return f'Accessibilité de l\'établissement "{self.erp.nom}" ({self.erp.code_postal})'
        else:
            return "Caractéristiques d'accessibilité de cet ERP"

    def get_history(self, exclude_changes_from=None):
        return _get_history(self.get_versions(),
                            exclude_changes_from=exclude_changes_from)

    def get_versions(self):
        # take the last n revisions
        qs = (Version.objects.get_for_object(self).select_related(
            "revision__user").order_by("-revision__date_created")
              [:self.HISTORY_MAX_LATEST_ITEMS + 1])

        # make it a list, so it's reversable
        versions = list(qs)
        # reorder the slice by date_created ASC
        versions.reverse()
        return versions

    def to_debug(self):
        cleaned = dict([(k, v)
                        for (k, v) in model_to_dict(self).copy().items()
                        if v is not None and v != "" and v != []])
        return json.dumps(cleaned, indent=2)

    def has_cheminement_ext(self):
        fields = schema.get_section_fields(schema.SECTION_CHEMINEMENT_EXT)
        return any(getattr(f) is not None for f in fields)

    def has_data(self):
        # count the number of filled fields to provide more validation
        for field_name in schema.get_a11y_fields():
            if hasattr(self, field_name):
                field_value = getattr(self, field_name)
                if field_value not in [None, "", []]:
                    return True
        return False

    @staticmethod
    def export_data_comma_headers():
        return ";".join([
            str(field_name) for field_name in schema.get_a11y_fields()
            if field_name not in (
                "commentaire",
                "transport_information",
                "entree_pmr_informations",
            )
        ])

    def export_data_comma(self):
        # count the number of filled fields to provide more validation
        fields = [
            getattr(self, field_name)
            for field_name in schema.get_a11y_fields()
            if field_name not in ("commentaire", "transport_information",
                                  "entree_pmr_informations")
        ]
        fl = list()
        for f in fields:
            if f is None or (isinstance(f, list) and len(f) == 0):
                fl.append("")
            else:
                fl.append(str(f).replace("\n", " ").replace(";", " "))
        return ";".join(fl)
Ejemplo n.º 23
0
class Resource(ModifiableModel, AutoIdentifiedModel):
    AUTHENTICATION_TYPES = (('none', _('None')), ('weak', _('Weak')),
                            ('strong', _('Strong')))
    ACCESS_CODE_TYPE_NONE = 'none'
    ACCESS_CODE_TYPE_PIN4 = 'pin4'
    ACCESS_CODE_TYPE_PIN6 = 'pin6'
    ACCESS_CODE_TYPES = (
        (ACCESS_CODE_TYPE_NONE, _('None')),
        (ACCESS_CODE_TYPE_PIN4, _('4-digit PIN code')),
        (ACCESS_CODE_TYPE_PIN6, _('6-digit PIN code')),
    )

    PRICE_TYPE_HOURLY = 'hourly'
    PRICE_TYPE_DAILY = 'daily'
    PRICE_TYPE_WEEKLY = 'weekly'
    PRICE_TYPE_FIXED = 'fixed'
    PRICE_TYPE_CHOICES = (
        (PRICE_TYPE_HOURLY, _('Hourly')),
        (PRICE_TYPE_DAILY, _('Daily')),
        (PRICE_TYPE_WEEKLY, _('Weekly')),
        (PRICE_TYPE_FIXED, _('Fixed')),
    )
    id = models.CharField(primary_key=True, max_length=100)
    public = models.BooleanField(default=True, verbose_name=_('Public'))
    unit = models.ForeignKey('Unit',
                             verbose_name=_('Unit'),
                             db_index=True,
                             null=True,
                             blank=True,
                             related_name="resources",
                             on_delete=models.PROTECT)
    type = models.ForeignKey(ResourceType,
                             verbose_name=_('Resource type'),
                             db_index=True,
                             on_delete=models.PROTECT)
    purposes = models.ManyToManyField(Purpose, verbose_name=_('Purposes'))
    name = models.CharField(verbose_name=_('Name'), max_length=200)
    description = models.TextField(verbose_name=_('Description'),
                                   null=True,
                                   blank=True)
    need_manual_confirmation = models.BooleanField(
        verbose_name=_('Need manual confirmation'), default=False)
    authentication = models.CharField(blank=False,
                                      verbose_name=_('Authentication'),
                                      max_length=20,
                                      choices=AUTHENTICATION_TYPES)
    people_capacity = models.PositiveIntegerField(
        verbose_name=_('People capacity'), null=True, blank=True)
    area = models.PositiveIntegerField(verbose_name=_('Area (m2)'),
                                       null=True,
                                       blank=True)

    # if not set, location is inherited from unit
    location = models.PointField(verbose_name=_('Location'),
                                 null=True,
                                 blank=True,
                                 srid=settings.DEFAULT_SRID)

    min_period = models.DurationField(
        verbose_name=_('Minimum reservation time'),
        default=datetime.timedelta(minutes=30))
    max_period = models.DurationField(
        verbose_name=_('Maximum reservation time'), null=True, blank=True)
    slot_size = models.DurationField(
        verbose_name=_('Slot size for reservation time'),
        default=datetime.timedelta(minutes=30))

    equipment = EquipmentField(Equipment,
                               through='ResourceEquipment',
                               verbose_name=_('Equipment'))
    max_reservations_per_user = models.PositiveIntegerField(
        verbose_name=_('Maximum number of active reservations per user'),
        null=True,
        blank=True)
    reservable = models.BooleanField(verbose_name=_('Reservable'),
                                     default=False)
    reservation_info = models.TextField(verbose_name=_('Reservation info'),
                                        null=True,
                                        blank=True)
    responsible_contact_info = models.TextField(
        verbose_name=_('Responsible contact info'), blank=True)
    generic_terms = models.ForeignKey(
        TermsOfUse,
        verbose_name=_('Generic terms'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='resources_where_generic_terms')
    payment_terms = models.ForeignKey(
        TermsOfUse,
        verbose_name=_('Payment terms'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='resources_where_payment_terms')
    specific_terms = models.TextField(verbose_name=_('Specific terms'),
                                      blank=True)
    reservation_requested_notification_extra = models.TextField(verbose_name=_(
        'Extra content to "reservation requested" notification'),
                                                                blank=True)
    reservation_confirmed_notification_extra = models.TextField(verbose_name=_(
        'Extra content to "reservation confirmed" notification'),
                                                                blank=True)
    min_price = models.DecimalField(
        verbose_name=_('Min price'),
        max_digits=8,
        decimal_places=2,
        blank=True,
        null=True,
        validators=[MinValueValidator(Decimal('0.00'))])
    max_price = models.DecimalField(
        verbose_name=_('Max price'),
        max_digits=8,
        decimal_places=2,
        blank=True,
        null=True,
        validators=[MinValueValidator(Decimal('0.00'))])

    price_type = models.CharField(max_length=32,
                                  verbose_name=_('price type'),
                                  choices=PRICE_TYPE_CHOICES,
                                  default=PRICE_TYPE_HOURLY)

    access_code_type = models.CharField(verbose_name=_('Access code type'),
                                        max_length=20,
                                        choices=ACCESS_CODE_TYPES,
                                        default=ACCESS_CODE_TYPE_NONE)
    # Access codes can be generated either by the general Respa code or
    # the Kulkunen app. Kulkunen will set the `generate_access_codes`
    # attribute by itself if special access code considerations are
    # needed.
    generate_access_codes = models.BooleanField(
        verbose_name=_('Generate access codes'),
        default=True,
        editable=False,
        help_text=_('Should access codes generated by the general system'))
    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)
    reservation_metadata_set = models.ForeignKey(
        'resources.ReservationMetadataSet',
        verbose_name=_('Reservation metadata set'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL)
    external_reservation_url = models.URLField(
        verbose_name=_('External reservation URL'),
        help_text=
        _('A link to an external reservation system if this resource is managed elsewhere'
          ),
        null=True,
        blank=True)
    reservation_extra_questions = models.TextField(
        verbose_name=_('Reservation extra questions'), blank=True)

    objects = ResourceQuerySet.as_manager()

    class Meta:
        verbose_name = _("resource")
        verbose_name_plural = _("resources")
        ordering = (
            'unit',
            'name',
        )

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

    @cached_property
    def main_image(self):
        resource_image = next(
            (image for image in self.images.all() if image.type == 'main'),
            None)

        return resource_image.image if resource_image else None

    def validate_reservation_period(self, reservation, user, data=None):
        """
        Check that given reservation if valid for given user.

        Reservation may be provided as Reservation or as a data dict.
        When providing the data dict from a serializer, reservation
        argument must be present to indicate the reservation being edited,
        or None if we are creating a new reservation.
        If the reservation is not valid raises a ValidationError.

        Staff members have no restrictions at least for now.

        Normal users cannot make multi day reservations or reservations
        outside opening hours.

        :type reservation: Reservation
        :type user: User
        :type data: dict[str, Object]
        """

        # no restrictions for staff
        if self.is_admin(user):
            return

        tz = self.unit.get_tz()
        # check if data from serializer is present:
        if data:
            begin = data['begin']
            end = data['end']
        else:
            # if data is not provided, the reservation object has the desired data:
            begin = reservation.begin
            end = reservation.end

        if begin.tzinfo:
            begin = begin.astimezone(tz)
        else:
            begin = tz.localize(begin)
        if end.tzinfo:
            end = end.astimezone(tz)
        else:
            end = tz.localize(end)

        if begin.date() != end.date():
            raise ValidationError(_("You cannot make a multi day reservation"))

        if not self.can_ignore_opening_hours(user):
            opening_hours = self.get_opening_hours(begin.date(), end.date())
            days = opening_hours.get(begin.date(), None)
            if days is None or not any(day['opens'] and begin >= day['opens']
                                       and end <= day['closes']
                                       for day in days):
                raise ValidationError(
                    _("You must start and end the reservation during opening hours"
                      ))

        if not self.can_ignore_max_period(user) and (
                self.max_period and (end - begin) > self.max_period):
            raise ValidationError(
                _("The maximum reservation length is %(max_period)s") %
                {'max_period': humanize_duration(self.max_period)})

    def validate_max_reservations_per_user(self, user):
        """
        Check maximum number of active reservations per user per resource.
        If the user has too many reservations raises ValidationError.

        Staff members have no reservation limits.

        :type user: User
        """
        if self.can_ignore_max_reservations_per_user(user):
            return

        max_count = self.max_reservations_per_user
        if max_count is not None:
            reservation_count = self.reservations.filter(
                user=user).active().count()
            if reservation_count >= max_count:
                raise ValidationError(
                    _("Maximum number of active reservations for this resource exceeded."
                      ))

    def check_reservation_collision(self, begin, end, reservation):
        overlapping = self.reservations.filter(end__gt=begin,
                                               begin__lt=end).active()
        if reservation:
            overlapping = overlapping.exclude(pk=reservation.pk)
        return overlapping.exists()

    def get_available_hours(self,
                            start=None,
                            end=None,
                            duration=None,
                            reservation=None,
                            during_closing=False):
        """
        Returns hours that the resource is not reserved for a given date range

        If include_closed=True, will also return hours when the resource is closed, if it is not reserved.
        This is so that admins can book resources during closing hours. Returns
        the available hours as a list of dicts. The optional reservation argument
        is for disregarding a given reservation during checking, if we wish to
        move an existing reservation. The optional duration argument specifies
        minimum length for periods to be returned.

        :rtype: list[dict[str, datetime.datetime]]
        :type start: datetime.datetime
        :type end: datetime.datetime
        :type duration: datetime.timedelta
        :type reservation: Reservation
        :type during_closing: bool
        """
        today = arrow.get(timezone.now())
        if start is None:
            start = today.floor('day').naive
        if end is None:
            end = today.replace(days=+1).floor('day').naive
        if not start.tzinfo and not end.tzinfo:
            """
            Only try to localize naive dates
            """
            tz = timezone.get_current_timezone()
            start = tz.localize(start)
            end = tz.localize(end)

        if not during_closing:
            """
            Check open hours only
            """
            open_hours = self.get_opening_hours(start, end)
            hours_list = []
            for date, open_during_date in open_hours.items():
                for period in open_during_date:
                    if period['opens']:
                        # if the start or end straddle opening hours
                        opens = period[
                            'opens'] if period['opens'] > start else start
                        closes = period[
                            'closes'] if period['closes'] < end else end
                        # include_closed to prevent recursion, opening hours need not be rechecked
                        hours_list.extend(
                            self.get_available_hours(start=opens,
                                                     end=closes,
                                                     duration=duration,
                                                     reservation=reservation,
                                                     during_closing=True))
            return hours_list

        reservations = self.reservations.filter(
            end__gte=start, begin__lte=end).order_by('begin')
        hours_list = [({'starts': start})]
        first_checked = False
        for res in reservations:
            # skip the reservation that is being edited
            if res == reservation:
                continue
            # check if the reservation spans the beginning
            if not first_checked:
                first_checked = True
                if res.begin < start:
                    if res.end > end:
                        return []
                    hours_list[0]['starts'] = res.end
                    # proceed to the next reservation
                    continue
            if duration:
                if res.begin - hours_list[-1]['starts'] < duration:
                    # the free period is too short, discard this period
                    hours_list[-1]['starts'] = res.end
                    continue
            hours_list[-1]['ends'] = timezone.localtime(res.begin)
            # check if the reservation spans the end
            if res.end > end:
                return hours_list
            hours_list.append({'starts': timezone.localtime(res.end)})
        # after the last reservation, we must check if the remaining free period is too short
        if duration:
            if end - hours_list[-1]['starts'] < duration:
                hours_list.pop()
                return hours_list
        # otherwise add the remaining free period
        hours_list[-1]['ends'] = end
        return hours_list

    def get_opening_hours(self,
                          begin=None,
                          end=None,
                          opening_hours_cache=None):
        """
        :rtype : dict[str, datetime.datetime]
        :type begin: datetime.date
        :type end: datetime.date
        """
        tz = pytz.timezone(self.unit.time_zone)
        begin, end = determine_hours_time_range(begin, end, tz)

        if opening_hours_cache is None:
            hours_objs = self.opening_hours.filter(
                open_between__overlap=(begin, end, '[)'))
        else:
            hours_objs = opening_hours_cache

        opening_hours = dict()
        for h in hours_objs:
            opens = h.open_between.lower.astimezone(tz)
            closes = h.open_between.upper.astimezone(tz)
            date = opens.date()
            hours_item = OrderedDict(opens=opens, closes=closes)
            date_item = opening_hours.setdefault(date, [])
            date_item.append(hours_item)

        # Set the dates when the resource is closed.
        date = begin.date()
        end = end.date()
        while date < end:
            if date not in opening_hours:
                opening_hours[date] = [OrderedDict(opens=None, closes=None)]
            date += datetime.timedelta(days=1)

        return opening_hours

    def update_opening_hours(self):
        hours = self.opening_hours.order_by('open_between')
        existing_hours = {}
        for h in hours:
            assert h.open_between.lower not in existing_hours
            existing_hours[h.open_between.lower] = h.open_between.upper

        unit_periods = list(self.unit.periods.all())
        resource_periods = list(self.periods.all())

        # Periods set for the resource always carry a higher priority. If
        # nothing is defined for the resource for a given day, use the
        # periods configured for the unit.
        for period in unit_periods:
            period.priority = 0
        for period in resource_periods:
            period.priority = 1

        earliest_date = None
        latest_date = None
        all_periods = unit_periods + resource_periods
        for period in all_periods:
            if earliest_date is None or period.start < earliest_date:
                earliest_date = period.start
            if latest_date is None or period.end > latest_date:
                latest_date = period.end

        # Assume we delete everything, but remove items from the delete
        # list if the hours are identical.
        to_delete = existing_hours
        to_add = {}
        if all_periods:
            hours = get_opening_hours(self.unit.time_zone, all_periods,
                                      earliest_date, latest_date)
            for hours_items in hours.values():
                for h in hours_items:
                    if not h['opens'] or not h['closes']:
                        continue
                    if h['opens'] in to_delete and h['closes'] == to_delete[
                            h['opens']]:
                        del to_delete[h['opens']]
                        continue
                    to_add[h['opens']] = h['closes']

        if to_delete:
            ret = ResourceDailyOpeningHours.objects.filter(
                open_between__in=[(opens, closes, '[)')
                                  for opens, closes in to_delete.items()],
                resource=self).delete()
            assert ret[0] == len(to_delete)

        add_objs = [
            ResourceDailyOpeningHours(resource=self,
                                      open_between=(opens, closes, '[)'))
            for opens, closes in to_add.items()
        ]
        if add_objs:
            ResourceDailyOpeningHours.objects.bulk_create(add_objs)

    def is_admin(self, user):
        """
        Check if the given user is an administrator of this resource.

        :type user: users.models.User
        :rtype: bool
        """
        # UserFilterBackend and ReservationFilterSet in resources.api.reservation assume the same behaviour,
        # so if this is changed those need to be changed as well.
        if not self.unit:
            return is_general_admin(user)
        return self.unit.is_admin(user)

    def is_manager(self, user):
        """
        Check if the given user is a manager of this resource.

        :type user: users.models.User
        :rtype: bool
        """
        if not self.unit:
            return False
        return self.unit.is_manager(user)

    def is_viewer(self, user):
        """
        Check if the given user is a viewer of this resource.

        :type user: users.models.User
        :rtype: bool
        """
        if not self.unit:
            return False
        return self.unit.is_viewer(user)

    def _has_perm(self, user, perm, allow_admin=True):
        if not is_authenticated_user(user):
            return False

        if (self.is_admin(user) and allow_admin) or user.is_superuser:
            return True

        return self._has_role_perm(user, perm) or self._has_explicit_perm(
            user, perm, allow_admin)

    def _has_explicit_perm(self, user, perm, allow_admin=True):
        if hasattr(self, '_permission_checker'):
            checker = self._permission_checker
        else:
            checker = ObjectPermissionChecker(user)

        # Permissions can be given per-unit
        if checker.has_perm('unit:%s' % perm, self.unit):
            return True
        # ... or through Resource Groups
        resource_group_perms = [
            checker.has_perm('group:%s' % perm, rg)
            for rg in self.groups.all()
        ]
        return any(resource_group_perms)

    def _has_role_perm(self, user, perm):
        allowed_roles = UNIT_ROLE_PERMISSIONS.get(perm)
        is_allowed = False

        if (UnitAuthorizationLevel.admin in allowed_roles
                or UnitGroupAuthorizationLevel.admin
                in allowed_roles) and not is_allowed:
            is_allowed = self.is_admin(user)

        if UnitAuthorizationLevel.manager in allowed_roles and not is_allowed:
            is_allowed = self.is_manager(user)

        if UnitAuthorizationLevel.viewer in allowed_roles and not is_allowed:
            is_allowed = self.is_viewer(user)

        return is_allowed

    def get_users_with_perm(self, perm):
        users = {
            u
            for u in get_users_with_perms(self.unit)
            if u.has_perm('unit:%s' % perm, self.unit)
        }
        for rg in self.groups.all():
            users |= {
                u
                for u in get_users_with_perms(rg)
                if u.has_perm('group:%s' % perm, rg)
            }
        return users

    def can_make_reservations(self, user):
        return self.reservable or self._has_perm(user, 'can_make_reservations')

    def can_modify_reservations(self, user):
        return self._has_perm(user, 'can_modify_reservations')

    def can_comment_reservations(self, user):
        return self._has_perm(user, 'can_comment_reservations')

    def can_ignore_opening_hours(self, user):
        return self._has_perm(user, 'can_ignore_opening_hours')

    def can_view_reservation_extra_fields(self, user):
        return self._has_perm(user, 'can_view_reservation_extra_fields')

    def can_view_reservation_user(self, user):
        return self._has_perm(user, 'can_view_reservation_user')

    def can_access_reservation_comments(self, user):
        return self._has_perm(user, 'can_access_reservation_comments')

    def can_view_reservation_catering_orders(self, user):
        return self._has_perm(user, 'can_view_reservation_catering_orders')

    def can_modify_reservation_catering_orders(self, user):
        return self._has_perm(user, 'can_modify_reservation_catering_orders')

    def can_view_reservation_product_orders(self, user):
        return self._has_perm(user,
                              'can_view_reservation_product_orders',
                              allow_admin=False)

    def can_modify_paid_reservations(self, user):
        return self._has_perm(user,
                              'can_modify_paid_reservations',
                              allow_admin=False)

    def can_approve_reservations(self, user):
        return self._has_perm(user,
                              'can_approve_reservation',
                              allow_admin=False)

    def can_view_reservation_access_code(self, user):
        return self._has_perm(user, 'can_view_reservation_access_code')

    def can_bypass_payment(self, user):
        return self._has_perm(user, 'can_bypass_payment')

    def can_create_staff_event(self, user):
        return self._has_perm(user, 'can_create_staff_event')

    def can_create_special_type_reservation(self, user):
        return self._has_perm(user, 'can_create_special_type_reservation')

    def can_bypass_manual_confirmation(self, user):
        return self._has_perm(user, 'can_bypass_manual_confirmation')

    def can_create_reservations_for_other_users(self, user):
        return self._has_perm(user, 'can_create_reservations_for_other_users')

    def can_create_overlapping_reservations(self, user):
        return self._has_perm(user, 'can_create_overlapping_reservations')

    def can_ignore_max_reservations_per_user(self, user):
        return self._has_perm(user, 'can_ignore_max_reservations_per_user')

    def can_ignore_max_period(self, user):
        return self._has_perm(user, 'can_ignore_max_period')

    def is_access_code_enabled(self):
        return self.access_code_type != Resource.ACCESS_CODE_TYPE_NONE

    def get_reservable_max_days_in_advance(self):
        return self.reservable_max_days_in_advance or self.unit.reservable_max_days_in_advance

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

    def get_reservable_min_days_in_advance(self):
        return self.reservable_min_days_in_advance or self.unit.reservable_min_days_in_advance

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

    def has_rent(self):
        return self.products.current().rents().exists()

    def get_supported_reservation_extra_field_names(self, cache=None):
        if not self.reservation_metadata_set_id:
            return []
        if cache:
            metadata_set = cache[self.reservation_metadata_set_id]
        else:
            metadata_set = self.reservation_metadata_set
        return [x.field_name for x in metadata_set.supported_fields.all()]

    def get_required_reservation_extra_field_names(self, cache=None):
        if not self.reservation_metadata_set:
            return []
        if cache:
            metadata_set = cache[self.reservation_metadata_set_id]
        else:
            metadata_set = self.reservation_metadata_set
        return [x.field_name for x in metadata_set.required_fields.all()]

    def clean(self):
        if self.min_price is not None and self.max_price is not None and self.min_price > self.max_price:
            raise ValidationError({
                'min_price':
                _('This value cannot be greater than max price')
            })
        if self.min_period % self.slot_size != datetime.timedelta(0):
            raise ValidationError({
                'min_period':
                _('This value must be a multiple of slot_size')
            })

        if self.need_manual_confirmation and self.products.current().exists():
            raise ValidationError({
                'need_manual_confirmation':
                _('This cannot be enabled because the resource has product(s).'
                  )
            })
Ejemplo n.º 24
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.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 (
            IntegerRangeField,
            BigIntegerRangeField,
            FloatRangeField,
            DateRangeField,
            DateTimeRangeField,
        )

        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()
            float_range = FloatRangeField()
            date_range = DateRangeField()
            datetime_range = DateTimeRangeField()
    except ImportError:
        # Skip PostgreSQL-related fields
        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()
Ejemplo n.º 25
0
class GeoeventsSource(models.Model):
    name = models.CharField(max_length=200)
    url = models.URLField(
        help_text='URL of service location. Requires JSONP support',
        max_length=500)
Ejemplo n.º 26
0
class Profile(ModelWithSlugMixin, TimeStampedModel):
    name = models.CharField(max_length=128,
                            blank=True,
                            help_text=_('The full name of the person or team'))
    slug = models.CharField(
        max_length=128,
        unique=True,
        blank=True,
        help_text=
        _('A short name that will be used in URLs for projects owned by this profile'
          ))
    email = models.EmailField(
        blank=True, help_text=_('Contact email address of the profile holder'))
    description = models.TextField(blank=True, default='')
    avatar_url = models.URLField(blank=True, null=True)
    # projects (reverse, Project)

    # User-profile specific
    auth = models.OneToOneField(settings.AUTH_USER_MODEL,
                                related_name='profile',
                                null=True,
                                blank=True,
                                on_delete=models.CASCADE)
    affiliation = models.CharField(max_length=256, blank=True, default='')
    teams = models.ManyToManyField('Profile',
                                   related_name='members',
                                   blank=True,
                                   limit_choices_to={'auth__isnull': True})

    # Team-profile specific
    # members (reverse, Profile)

    # Feature flags/versions
    class Versions:
        AMETHYST = 1
        BISTRE = 2

    PROJECT_EDITOR_VERSION_CHOICES = (
        (Versions.AMETHYST, "Amethyst"),
        (Versions.BISTRE, "Bistre"),
    )

    project_editor_version = models.PositiveIntegerField(
        choices=PROJECT_EDITOR_VERSION_CHOICES, default=Versions.BISTRE)

    objects = ProfileManager()

    def __str__(self):
        return self.slug if self.auth is None else self.auth.username

    def natural_key(self):
        return (self.slug, )

    def get_slug_basis(self):
        return self.name

    def get_all_slugs(self):
        return set([p['slug'] for p in Profile.objects.all().values('slug')])

    def slug_exists(self, slug):
        return Profile.objects.filter(slug__iexact=slug).exists()

    def is_user_profile(self):
        return self.auth is not None

    def is_owned_by(self, user):
        return (user.id == self.auth_id)

    def has_member(self, user):
        members = list(self.members.all())
        if user.id in [profile.auth_id for profile in members]:
            return True
        else:
            return any(profile.has_member(user) for profile in members)

    def is_synced_with_auth(self, auth=None):
        auth = auth or self.auth
        if self.email != auth.email:
            return False
        return True

    def save(self, **kwargs):
        super(Profile, self).save(**kwargs)
        if self.auth and not self.is_synced_with_auth():
            self.auth.username = self.slug
            self.auth.email = self.email
            self.auth.save()

    def authorizes(self, user):
        """
        Test whether a given authenticated user is allowed to perform
        actions on behalf of this profile.
        """
        if user.is_superuser:
            return True

        if self.is_owned_by(user):
            return True

        if self.has_member(user):
            return True

        return False
Ejemplo n.º 27
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=_(u"Description teaser"),
        blank=True,
        help_text=_(u"A brief summary"),
        db_column='chapeau')
    description = models.TextField(verbose_name=_(u"Description"),
                                   blank=True,
                                   db_column='description',
                                   help_text=_(u"Complete description"))
    themes = models.ManyToManyField(Theme,
                                    related_name="touristiccontents",
                                    db_table="t_r_contenu_touristique_theme",
                                    blank=True,
                                    verbose_name=_(u"Themes"),
                                    help_text=_(u"Main theme(s)"))
    geom = models.GeometryField(verbose_name=_(u"Location"),
                                srid=settings.SRID)
    category = models.ForeignKey(TouristicContentCategory,
                                 related_name='contents',
                                 verbose_name=_(u"Category"),
                                 db_column='categorie')
    contact = models.TextField(verbose_name=_(u"Contact"),
                               blank=True,
                               db_column='contact',
                               help_text=_(u"Address, phone, etc."))
    email = models.EmailField(verbose_name=_(u"Email"),
                              max_length=256,
                              db_column='email',
                              blank=True,
                              null=True)
    website = models.URLField(verbose_name=_(u"Website"),
                              max_length=256,
                              db_column='website',
                              blank=True,
                              null=True)
    practical_info = models.TextField(verbose_name=_(u"Practical info"),
                                      blank=True,
                                      db_column='infos_pratiques',
                                      help_text=_(u"Anything worth to know"))
    type1 = models.ManyToManyField(TouristicContentType,
                                   related_name='contents1',
                                   verbose_name=_(u"Type 1"),
                                   db_table="t_r_contenu_touristique_type1",
                                   blank=True)
    type2 = models.ManyToManyField(TouristicContentType,
                                   related_name='contents2',
                                   verbose_name=_(u"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=_(u"External id"),
                           max_length=128,
                           blank=True,
                           null=True,
                           db_column='id_externe')
    reservation_system = models.ForeignKey(
        ReservationSystem,
        verbose_name=_(u"Reservation system"),
        blank=True,
        null=True)
    reservation_id = models.CharField(verbose_name=_(u"Reservation ID"),
                                      max_length=128,
                                      blank=True,
                                      db_column='id_reservation')
    approved = models.BooleanField(verbose_name=_(u"Approved"),
                                   default=False,
                                   db_column='labellise')

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

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

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_document_public_url(self):
        """ Override ``geotrek.common.mixins.PublishableMixin``
        """
        return ('tourism:touristiccontent_document_public', [], {
            'lang': get_language(),
            'pk': self.pk,
            'slug': self.slug
        })

    @property
    def districts_display(self):
        return ', '.join([unicode(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([unicode(n) for n in self.type1.all()])

    @property
    def type2_display(self):
        return ', '.join([unicode(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([unicode(portal) for portal in self.portal.all()])

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

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

    @property
    def extent(self):
        return self.geom.buffer(10).transform(settings.API_SRID,
                                              clone=True).extent
Ejemplo n.º 28
0
class Project(ModelWithSlugMixin, CloneableModelMixin, TimeStampedModel):
    STATUS_CHOICES = (
        ('not-started', _('Not Started')),
        ('active', _('Active')),
        ('complete', _('Complete')),
    )

    LINK_TYPE_CHOICES = (
        ('event', _('Event')),
        ('section', _('Section')),
        ('external', _('External URL')),
    )

    LAYOUT_CHOICES = (
        ('generic', _('Default (classic)')),
        ('shareabouts', _('Shareabouts Map')),
    )

    title = models.TextField(blank=True)
    slug = models.CharField(max_length=128, blank=True)
    public = models.BooleanField(default=False, blank=True)
    status = models.CharField(
        help_text=_("A string representing the project's status"),
        choices=STATUS_CHOICES,
        default='not-started',
        max_length=32,
        blank=True)
    location = models.TextField(help_text=_(
        "The general location of the project, e.g. \"Philadelphia, PA\", \"Clifton Heights, Louisville, KY\", \"4th St. Corridor, Brooklyn, NY\", etc."
    ),
                                default='',
                                blank=True)
    contact = models.TextField(
        help_text=_("The contact information for the project"),
        default='',
        blank=True)
    owner = models.ForeignKey('Profile', related_name='projects')

    details = JSONField(blank=True, default=dict)
    theme = models.ForeignKey('Theme',
                              related_name='projects',
                              null=True,
                              blank=True,
                              on_delete=models.SET_NULL)
    layout = models.CharField(max_length=20,
                              choices=LAYOUT_CHOICES,
                              default='generic')
    cover_img_url = models.URLField(_('Cover Image URL'),
                                    blank=True,
                                    max_length=2048)
    logo_img_url = models.URLField(_('Logo Image URL'),
                                   blank=True,
                                   max_length=2048)
    template = models.ForeignKey(
        'Project',
        help_text=_("The project, if any, that this one is based off of"),
        null=True,
        blank=True,
        on_delete=models.SET_NULL)

    geometry = models.GeometryField(null=True, blank=True)

    expires_at = models.DateTimeField(null=True, blank=True)
    payment_type = models.CharField(max_length=20, blank=True)
    customer = models.OneToOneField('moonclerk.Customer',
                                    blank=True,
                                    null=True,
                                    related_name='project')
    payments = GenericRelation('moonclerk.Payment',
                               content_type_field='item_type',
                               object_id_field='item_id')

    # NOTE: These may belong in a separate model, but are on the project for
    #       now. I think the model would be called a Highlight.
    happening_now_description = models.TextField(blank=True)
    happening_now_link_type = models.CharField(max_length=16,
                                               choices=LINK_TYPE_CHOICES,
                                               blank=True)
    happening_now_link_url = models.CharField(max_length=2048, blank=True)

    get_involved_description = models.TextField(blank=True)
    get_involved_link_type = models.CharField(max_length=16,
                                              choices=LINK_TYPE_CHOICES,
                                              blank=True)
    get_involved_link_url = models.CharField(max_length=2048, blank=True)

    # Project activity
    last_opened_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                       null=True,
                                       blank=True,
                                       related_name='+')
    last_opened_at = models.DateTimeField(null=True, blank=True)
    last_saved_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                      null=True,
                                      blank=True,
                                      related_name='+')
    last_saved_at = models.DateTimeField(null=True, blank=True)

    objects = ProjectManager()

    class Meta:
        unique_together = [('owner', 'slug')]

    def __str__(self):
        return self.title

    def get_summary(self):
        for section in self.sections.all():
            if section.type == 'text':
                return section.details.get('content', '')

    def mark_opened_by(self, user, opened_at=None):
        # TODO: This could just be done in the cache.
        self.last_opened_at = opened_at or now()
        self.last_opened_by = user if (user
                                       and user.is_authenticated()) else None
        self.save()

    def mark_closed(self):
        self.mark_opened_by(None)

    def is_opened_by(self, user):
        two_minutes = timedelta(minutes=2)
        return self.last_opened_by == user and (
            now() - self.last_opened_at) < two_minutes

    def get_opened_by(self):
        two_minutes = timedelta(minutes=2)
        if self.last_opened_at and (now() - self.last_opened_at) < two_minutes:
            return self.last_opened_by
        else:
            return None

    def natural_key(self):
        return self.owner.natural_key() + (self.slug, )

    def get_slug_basis(self):
        """
        Get the string off that will be slugified to construct the slug.
        """
        return self.title

    def get_all_slugs(self):
        """
        Generate the set of all mututally unique slugs with respect to this
        model.
        """
        return [p.slug for p in self.owner.projects.all()]

    def slug_exists(self, slug):
        return self.owner.projects.filter(slug__iexact=slug).exists()

    def clone(self, *args, **kwargs):
        new_inst = super(Project, self).clone(*args, **kwargs)
        for e in self.events.all():
            e.clone(project=new_inst)
        for s in self.sections.all():
            s.clone(project=new_inst)
        return new_inst

    def owned_by(self, obj):
        UserAuth = auth.get_user_model()
        if isinstance(obj, UserAuth):
            try:
                obj = obj.profile
            except Profile.DoesNotExist:
                return False
        return (self.owner == obj)

    def editable_by(self, obj):
        UserAuth = auth.get_user_model()
        if hasattr(obj, 'is_authenticated') and not obj.is_authenticated():
            return False

        if isinstance(obj, UserAuth):
            try:
                obj = obj.profile
            except Profile.DoesNotExist:
                return False

        if obj.auth.is_superuser:
            return True

        return self.owned_by(obj) or (obj in self.owner.members.all())

    def reset_trial_period(self):
        if hasattr(settings, 'TRIAL_DURATION'):
            duration = settings.TRIAL_DURATION
            if not isinstance(duration, timedelta):
                duration = timedelta(seconds=duration)
            self.expires_at = now() + duration

    def save(self, *args, **kwargs):
        if self.pk is None:  # Creating...
            if self.expires_at is None:
                self.reset_trial_period()
        return super(Project, self).save(*args, **kwargs)
Ejemplo n.º 29
0
class InformationDesk(models.Model):

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

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

    geom = models.PointField(verbose_name=_(u"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 = _(u"Information desk")
        verbose_name_plural = _(u"Information desks")
        ordering = ['name']

    def __unicode__(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 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)
class TrafficSignReal(
        DecimalValueFromDeviceTypeMixin,
        SourceControlModel,
        SoftDeleteModel,
        UserControlModel,
):
    id = models.UUIDField(primary_key=True,
                          unique=True,
                          editable=False,
                          default=uuid.uuid4)
    traffic_sign_plan = models.ForeignKey(
        TrafficSignPlan,
        verbose_name=_("Traffic Sign Plan"),
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    location = models.PointField(_("Location (3D)"), dim=3, srid=settings.SRID)
    height = models.IntegerField(_("Height"), blank=True, null=True)
    direction = models.IntegerField(_("Direction"), default=0)
    device_type = models.ForeignKey(
        TrafficControlDeviceType,
        verbose_name=_("Device type"),
        on_delete=models.PROTECT,
        limit_choices_to=Q(
            Q(target_model=None)
            | Q(target_model=DeviceTypeTargetModel.TRAFFIC_SIGN)),
        blank=False,
        null=True,
    )
    value = models.DecimalField(
        _("Traffic Sign Code value"),
        max_digits=10,
        decimal_places=2,
        blank=True,
        null=True,
    )
    legacy_code = models.CharField(_("Legacy Traffic Sign Code"),
                                   max_length=32,
                                   blank=True,
                                   null=True)
    txt = models.CharField(_("Txt"), max_length=254, blank=True, null=True)
    mount_real = models.ForeignKey(
        MountReal,
        verbose_name=_("Mount Real"),
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    mount_type = models.ForeignKey(
        MountType,
        verbose_name=_("Mount type"),
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
    )
    installation_date = models.DateField(_("Installation date"),
                                         blank=True,
                                         null=True)
    installation_status = EnumField(
        InstallationStatus,
        verbose_name=_("Installation status"),
        max_length=10,
        blank=True,
        null=True,
    )
    installation_id = models.CharField(_("Installation id"),
                                       max_length=254,
                                       blank=True,
                                       null=True)
    installation_details = models.CharField(_("Installation details"),
                                            max_length=254,
                                            blank=True,
                                            null=True)
    permit_decision_id = models.CharField(_("Permit decision id"),
                                          max_length=254,
                                          blank=True,
                                          null=True)
    validity_period_start = models.DateField(_("Validity period start"),
                                             blank=True,
                                             null=True)
    validity_period_end = models.DateField(_("Validity period end"),
                                           blank=True,
                                           null=True)
    condition = EnumIntegerField(
        Condition,
        verbose_name=_("Condition"),
        blank=True,
        null=True,
    )
    coverage_area = models.ForeignKey(
        CoverageArea,
        verbose_name=_("Coverage area"),
        blank=True,
        null=True,
        on_delete=models.PROTECT,
    )
    scanned_at = models.DateTimeField(_("Scanned at"), blank=True, null=True)
    size = EnumField(
        Size,
        verbose_name=_("Size"),
        max_length=1,
        blank=True,
        null=True,
    )
    reflection_class = EnumField(
        Reflection,
        verbose_name=_("Reflection"),
        max_length=2,
        blank=True,
        null=True,
    )
    surface_class = EnumField(
        Surface,
        verbose_name=_("Surface"),
        max_length=6,
        blank=True,
        null=True,
    )
    seasonal_validity_period_start = models.DateField(
        _("Seasonal validity period start"), blank=True, null=True)
    seasonal_validity_period_end = models.DateField(
        _("Seasonal validity period end"), blank=True, null=True)
    owner = models.ForeignKey(
        "traffic_control.Owner",
        verbose_name=_("Owner"),
        blank=False,
        null=False,
        on_delete=models.PROTECT,
    )
    manufacturer = models.CharField(_("Manufacturer"),
                                    max_length=254,
                                    blank=True,
                                    null=True)
    rfid = models.CharField(_("RFID"), max_length=254, blank=True, null=True)
    lifecycle = EnumIntegerField(Lifecycle,
                                 verbose_name=_("Lifecycle"),
                                 default=Lifecycle.ACTIVE)
    road_name = models.CharField(_("Road name"),
                                 max_length=254,
                                 blank=True,
                                 null=True)
    lane_number = EnumField(LaneNumber,
                            verbose_name=_("Lane number"),
                            default=LaneNumber.MAIN_1,
                            blank=True)
    lane_type = EnumField(
        LaneType,
        verbose_name=_("Lane type"),
        default=LaneType.MAIN,
        blank=True,
    )
    location_specifier = EnumIntegerField(
        LocationSpecifier,
        verbose_name=_("Location specifier"),
        default=LocationSpecifier.RIGHT,
        blank=True,
        null=True,
    )
    operation = models.CharField(_("Operation"),
                                 max_length=64,
                                 blank=True,
                                 null=True)
    attachment_url = models.URLField(_("Attachment url"),
                                     max_length=500,
                                     blank=True,
                                     null=True)

    objects = TrafficSignRealQuerySet.as_manager()

    class Meta:
        db_table = "traffic_sign_real"
        verbose_name = _("Traffic Sign Real")
        verbose_name_plural = _("Traffic Sign Reals")
        unique_together = ["source_name", "source_id"]

    def __str__(self):
        return f"{self.id} {self.device_type}"

    def save(self, *args, **kwargs):
        if self.device_type and not self.device_type.validate_relation(
                DeviceTypeTargetModel.TRAFFIC_SIGN):
            raise ValidationError(
                f'Device type "{self.device_type}" is not allowed for traffic signs'
            )

        if not self.device_type:
            self.device_type = (
                TrafficControlDeviceType.objects.for_target_model(
                    DeviceTypeTargetModel.TRAFFIC_SIGN).filter(
                        legacy_code=self.legacy_code).order_by("code").first())

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

    def has_additional_signs(self):
        return self.additional_signs.active().exists()

    @transaction.atomic
    def soft_delete(self, user):
        super().soft_delete(user)
        self.additional_signs.soft_delete(user)