class Person(AuditModel): person_guid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name="Person UUID") first_name = models.CharField( max_length=100, db_comment= ('Legal first name of the well driller or well pump installer who has applied and/or is ' 'registered with the province.')) surname = models.CharField( max_length=100, db_comment= ('Legal last name of the well driller or well pump installer who has applied and/or is ' 'registered with the province.')) # As per D.A. - temporary fields to hold compliance-related details well_driller_orcs_no = models.CharField( max_length=25, blank=True, null=True, verbose_name='ORCS File # reference (in context of Well Driller).', db_comment= ('Well driller\'s unique filing number used in the BC government Operational Records ' 'Classification Systems (ORCS) filing system. Each person has an ORCS number when a file ' 'is started with their correspondence, usually with the application for being ' 'registered. E.g. 3800-25/PUMP DRI W. The standard format for this number is ' '3800-25/DRI {first 4 characters of last name} {initial of first name}.' )) pump_installer_orcs_no = models.CharField( max_length=25, blank=True, null=True, verbose_name='ORCS File # reference (in context of Pump Installer).', db_comment= ('Well pump installer\'s unique filing number used in the BC government Operational ' 'Records Classification Systems (ORCS) filing system. Each person has an ORCS number ' 'when a file is started with their correspondence, usually with the application for ' 'being registered. Each person can have a unique ORCS number as a well pump installer ' 'and as a well driller. E.g. 3800-25/PUMP PRIC W. The standard format for this number ' 'is 3800-25/PUMP {first 4 characters of last name} {initial of first name}.' )) # contact information contact_tel = models.CharField( blank=True, null=True, max_length=15, verbose_name='Contact telephone number', db_comment= ('Land line area code and 7 digit phone number provided by the well driller or well pump ' 'installer where they can be contacted.')) contact_cell = models.CharField( blank=True, null=True, max_length=15, verbose_name='Contact cell number', db_comment= ('Cell phone area code and 7 digit number provided by the well driller or well pump ' 'installer where they can be contacted.')) contact_email = models.EmailField( blank=True, null=True, verbose_name='Email address', db_comment='Email address for the well driller or well pump installer.' ) effective_date = models.DateTimeField( default=timezone.now, null=False, db_comment= 'The date when the registries person record became available for use.') expiry_date = models.DateTimeField( default=timezone.make_aware(timezone.datetime.max, timezone.get_default_timezone()), null=False, db_comment= 'The date and time after which the record is no longer valid and should not be used.' ) history = GenericRelation(Version) class Meta: db_table = 'registries_person' ordering = ['first_name', 'surname'] verbose_name_plural = 'People' db_table_comment = 'Placeholder table comment.' def __str__(self): return '%s %s' % (self.first_name, self.surname) @property def name(self): return '%s %s' % (self.first_name, self.surname)
class RemoteCkanDataset(models.Model): class Meta(object): verbose_name = "Jeu de données moissonné" verbose_name_plural = "Jeux de données moissonnés" unique_together = ('remote_instance', 'dataset') remote_instance = models.ForeignKey( to='RemoteCkan', on_delete=models.CASCADE, to_field='id', ) dataset = models.ForeignKey( to='Dataset', on_delete=models.CASCADE, to_field='id', ) remote_dataset = models.UUIDField( verbose_name="Ckan UUID", editable=False, null=True, blank=True, unique=True, ) remote_organisation = models.SlugField( verbose_name="Organisation distante", max_length=100, blank=True, null=True, ) created_by = models.ForeignKey( User, related_name='creates_dataset_from_remote_ckan', verbose_name="Utilisateur", null=True, on_delete=models.SET_NULL, ) created_on = models.DateTimeField( verbose_name="Créé le", auto_now_add=True, ) updated_on = models.DateTimeField( verbose_name="Mis-à-jour le", auto_now_add=True, ) def __str__(self): return '{0} - {1}'.format(self.remote_instance, self.dataset) @property def url(self): base_url = self.remote_instance.url if not base_url.endswith('/'): base_url += '/' return reduce(urljoin, [base_url, 'dataset/', str(self.remote_dataset)])
class Order(models.Model): """ processing_fee should default to the maximum of base fees in the order but can then be edited mannually """ class OrderStatus(models.TextChoices): DRAFT = 'DRAFT', _('Draft') PENDING = 'PENDING', _('Pending') QUOTE_DONE = 'QUOTE_DONE', _('Quote done') READY = 'READY', _('Ready') IN_EXTRACT = 'IN_EXTRACT', _('In extract') PARTIALLY_DELIVERED = 'PARTIALLY_DELIVERED', _('Partially delivered') PROCESSED = 'PROCESSED', _('Processed') ARCHIVED = 'ARCHIVED', _('Archived') REJECTED = 'REJECTED', _('Rejected') title = models.CharField( _('title'), max_length=255, validators=[ RegexValidator( regex=r'^[^<>%$"\(\)\n\r]*$', message=_('Title contains forbidden characters'), ), ]) description = models.TextField(_('description'), blank=True) processing_fee = MoneyField(_('processing_fee'), max_digits=14, decimal_places=2, default_currency='CHF', blank=True, null=True) total_without_vat = MoneyField(_('total_without_vat'), max_digits=14, decimal_places=2, default_currency='CHF', blank=True, null=True) part_vat = MoneyField(_('part_vat'), max_digits=14, decimal_places=2, default_currency='CHF', blank=True, null=True) total_with_vat = MoneyField(_('total_with_vat'), max_digits=14, decimal_places=2, default_currency='CHF', blank=True, null=True) geom = models.PolygonField(_('geom'), srid=settings.DEFAULT_SRID) client = models.ForeignKey(UserModel, models.PROTECT, verbose_name=_('client'), blank=True) invoice_contact = models.ForeignKey(Contact, models.PROTECT, verbose_name=_('invoice_contact'), related_name='invoice_contact', blank=True, null=True) invoice_reference = models.CharField(_('invoice_reference'), max_length=255, blank=True) email_deliver = models.EmailField(_('email_deliver'), max_length=254, blank=True, null=True) order_type = models.ForeignKey(OrderType, models.PROTECT, verbose_name=_('order_type')) status = models.CharField(_('status'), max_length=20, choices=OrderStatus.choices, default=OrderStatus.DRAFT) date_ordered = models.DateTimeField(_('date_ordered'), blank=True, null=True) date_downloaded = models.DateTimeField(_('date_downloaded'), blank=True, null=True) date_processed = models.DateTimeField(_('date_processed'), blank=True, null=True) extract_result = models.FileField(upload_to='extract', null=True, blank=True) download_guid = models.UUIDField(_('download_guid'), null=True, blank=True) class Meta: db_table = 'order' ordering = ['-date_ordered'] verbose_name = _('order') def _reset_prices(self): self.processing_fee = None self.total_without_vat = None self.part_vat = None self.total_with_vat = None def set_price(self): """ Sets price information if all items have prices """ self._reset_prices() items = self.items.all() if items == []: return False self.total_without_vat = Money(0, 'CHF') self.processing_fee = Money(0, 'CHF') for item in items: if item.base_fee is None: self._reset_prices() return False if item.base_fee > self.processing_fee: self.processing_fee = item.base_fee self.total_without_vat += item.price self.total_without_vat += self.processing_fee self.part_vat = self.total_without_vat * settings.VAT self.total_with_vat = self.total_without_vat + self.part_vat return True def quote_done(self): """Admins confirmation they have given a manual price""" price_is_set = self.set_price() if price_is_set: self.status = self.OrderStatus.QUOTE_DONE self.save() send_geoshop_email(_('Geoshop - Quote has been done'), recipient=self.email_deliver or self.client.identity, template_name='email_quote_done', template_data={ 'order_id': self.id, 'first_name': self.client.identity.first_name, 'last_name': self.client.identity.last_name }) return price_is_set def _expand_product_groups(self): """ When a product is a group of products, the group is deleted from cart and is replaced with one OrderItem for each product inside the group. """ items = self.items.all() for item in items: # if product is a group (if product has children) if item.product.products.exists(): for product in item.product.products.all(): # only pick products that intersect current order geom if product.geom.intersects(self.geom): new_item = OrderItem(order=self, product=product, data_format=item.data_format) # If the data format for the group is not available for the item, # pick the first possible if item.data_format not in item.available_formats: new_item.data_format = product.product_formats.all( ).first().data_format new_item.set_price() new_item.save() item.delete() def confirm(self): """Customer's confirmations he wants to proceed with the order""" self._expand_product_groups() items = self.items.all() has_all_prices_calculated = True for item in items: if item.price_status == OrderItem.PricingStatus.PENDING: item.ask_price() has_all_prices_calculated = has_all_prices_calculated and False else: item.status = OrderItem.OrderItemStatus.IN_EXTRACT if has_all_prices_calculated: self.date_ordered = timezone.now() self.download_guid = uuid.uuid4() self.status = Order.OrderStatus.READY else: self.status = Order.OrderStatus.PENDING def next_status_on_extract_input(self): """Controls status when Extract uploads a file or cancel an order item""" previous_accepted_status = [ Order.OrderStatus.READY, Order.OrderStatus.IN_EXTRACT, Order.OrderStatus.PARTIALLY_DELIVERED ] if self.status not in previous_accepted_status: raise Exception("Order has an inappropriate status after input") items_statuses = set(self.items.all().values_list('status', flat=True)) if OrderItem.OrderItemStatus.IN_EXTRACT in items_statuses: if OrderItem.OrderItemStatus.PROCESSED in items_statuses: self.status = Order.OrderStatus.PARTIALLY_DELIVERED else: self.status = Order.OrderStatus.READY else: if OrderItem.OrderItemStatus.PROCESSED in items_statuses: self.status = Order.OrderStatus.PROCESSED self.date_processed = timezone.now() send_geoshop_email(_('Geoshop - Download ready'), recipient=self.email_deliver or self.client.identity, template_name='email_download_ready', template_data={ 'order_id': self.id, 'download_guid': self.download_guid, 'front_url': '{}://{}{}'.format( settings.FRONT_PROTOCOL, settings.FRONT_URL, settings.FRONT_HREF), 'first_name': self.client.identity.first_name, 'last_name': self.client.identity.last_name, }) else: self.status = Order.OrderStatus.REJECTED return self.status @property def geom_srid(self): return self.geom.srid @property def geom_area(self): return self.geom.area def __str__(self): return '%s - %s' % (self.id, self.title)
class Signal(CreatedUpdatedModel): SOURCE_DEFAULT_ANONYMOUS_USER = '******' # we need an unique id for external systems. # TODO SIG-563 rename `signal_id` to `signal_uuid` to be more specific. signal_id = models.UUIDField(default=uuid.uuid4, db_index=True) source = models.CharField(max_length=128, default=SOURCE_DEFAULT_ANONYMOUS_USER) text = models.CharField(max_length=3000) text_extra = models.CharField(max_length=10000, default='', blank=True) location = models.OneToOneField('signals.Location', related_name='signal', null=True, on_delete=models.SET_NULL) status = models.OneToOneField('signals.Status', related_name='signal', null=True, on_delete=models.SET_NULL) category_assignment = models.OneToOneField('signals.CategoryAssignment', related_name='signal', null=True, on_delete=models.SET_NULL) categories = models.ManyToManyField('signals.Category', through='signals.CategoryAssignment') reporter = models.OneToOneField('signals.Reporter', related_name='signal', null=True, on_delete=models.SET_NULL) priority = models.OneToOneField('signals.Priority', related_name='signal', null=True, on_delete=models.SET_NULL) # Date of the incident. incident_date_start = models.DateTimeField(null=False) incident_date_end = models.DateTimeField(null=True) # Date action is expected operational_date = models.DateTimeField(null=True) # Date we should have reported back to reporter. expire_date = models.DateTimeField(null=True) # file will be saved to MEDIA_ROOT/uploads/2015/01/30 upload = ArrayField(models.FileField(upload_to='uploads/%Y/%m/%d/'), null=True) # TODO: remove extra_properties = JSONField(null=True) # SIG-884 parent = models.ForeignKey(to='self', related_name='children', null=True, blank=True, on_delete=models.SET_NULL) objects = SignalQuerySet.as_manager() actions = SignalManager() @property def image(self): """ Field for backwards compatibility. The attachment table replaces the old 'image' property """ attachment = self.attachments.filter(is_image=True).first() return attachment.file if attachment else "" @property def image_crop(self): attachment = self.attachments.filter(is_image=True).first() return attachment.image_crop if self.image else '' class Meta: permissions = ( ('sia_read', 'Can read from SIA'), ('sia_write', 'Can write to SIA'), ('sia_split', 'Can split a signal into a parent with X children'), ('sia_signal_create_initial', 'Can create new signals'), ('sia_signal_create_note', 'Can create notes for signals'), ('sia_signal_change_status', 'Can change the status of a signal'), ('sia_signal_change_category', 'Can change the category of a signal'), ('sia_signal_export', 'Can export signals'), ('sia_signal_report', 'Can create reports for signals'), ) ordering = ('created_at',) indexes = [ models.Index(fields=['created_at']), models.Index(fields=['id', 'parent']), ] def __init__(self, *args, **kwargs): super(Signal, self).__init__(*args, **kwargs) if not self.signal_id: self.signal_id = uuid.uuid4() def __str__(self): """Identifying string. DO NOT expose sensitive stuff here. """ state = '' buurt_code = '' if self.status: state = self.status.state if self.location: buurt_code = self.location.buurt_code return '{} - {} - {} - {}'.format( self.id, state, buurt_code, self.created_at ) @property def sia_id(self): """SIA identifier used for external communication. :returns: str """ return 'SIA-{id}'.format(id=self.id) def get_fqdn_image_crop_url(self): """Get FQDN image crop url. :returns: url (str) or None """ if not self.image_crop: return None is_swift = isinstance(self.image_crop.storage, SwiftStorage) if is_swift: return self.image_crop.url # Generated temp url from Swift Object Store. else: # Generating a fully qualified url ourself. current_site = Site.objects.get_current() is_local = 'localhost' in current_site.domain or settings.DEBUG fqdn_url = '{scheme}://{domain}{path}'.format( scheme='http' if is_local else 'https', domain=current_site.domain, path=self.image_crop.url) return fqdn_url def is_parent(self): # If we have children we are a parent return self.children.exists() def is_child(self): # If we have a parent we are a child return self.parent is not None @property def siblings(self): if self.is_child(): # If we are a child return all siblings siblings_qs = self.parent.children.all() if self.pk: # Exclude myself if possible return siblings_qs.exclude(pk=self.pk) return siblings_qs # Return a non queryset return self.__class__.objects.none() def _validate(self): if self.is_parent() and self.is_child(): # We cannot be a parent and a child at once raise ValidationError('Cannot be a parent and a child at the once') if self.parent and self.parent.is_child(): # The parent of this Signal cannot be a child of another Signal raise ValidationError('A child of a child is not allowed') if (self.pk is None and self.is_child() and self.siblings.count() >= settings.SIGNAL_MAX_NUMBER_OF_CHILDREN): # we are a new child and our parent already has the max number of children raise ValidationError('Maximum number of children reached for the parent Signal') if self.children.exists() and self.status.state != workflow.GESPLITST: # If we have children our status can only be "gesplitst" raise ValidationError('The status of a parent Signal can only be "gesplitst"') def save(self, *args, **kwargs): self._validate() super(Signal, self).save(*args, **kwargs)
class Track(TimeStampedModel): class Meta: abstract = True name = models.CharField(max_length=100) description = models.TextField(blank=True) image = ThumbnailerImageField(upload_to=get_image_path, blank=True, null=True) # Main activity of the track activity_type = models.ForeignKey(ActivityType, default=1, on_delete=models.SET_DEFAULT) # link to athlete athlete = models.ForeignKey("Athlete", on_delete=models.CASCADE, related_name="tracks") # elevation gain in m total_elevation_gain = models.FloatField("Total elevation gain in m", default=0) # elevation loss in m total_elevation_loss = models.FloatField("Total elevation loss in m", default=0) # route distance in m total_distance = models.FloatField("Total length of the track in m", default=0) # geographic information geom = models.LineStringField("line geometry", srid=3857) # Start and End-place start_place = models.ForeignKey(Place, null=True, related_name="starts_%(class)s", on_delete=models.SET_NULL) end_place = models.ForeignKey(Place, null=True, related_name="ends_%(class)s", on_delete=models.SET_NULL) # uuid field to generate unique file names uuid = models.UUIDField(default=uuid4, editable=False) # track data as a pandas DataFrame data = DataFrameField(null=True, upload_to=athlete_data_directory_path, unique_fields=["uuid"]) def calculate_step_distances(self, min_distance: float, commit=True): """ calculate distance between each row, removing steps where distance is too small. """ data = self.data.copy() data["geom"], srid = self.geom, self.geom.srid data.drop(data[data.distance.diff() < min_distance].index, inplace=True) data["step_distance"] = data.distance.diff() try: self.geom = LineString(data.geom.tolist(), srid=srid) except ValueError: message = "Cannot clean track data: invalid distance values." logger.error(message, exc_info=True) raise ValueError(message) data.drop(columns=["geom"], inplace=True) self.data = data.fillna(value=0) if commit: self.save(update_fields=["data", "geom"]) def calculate_gradients(self, max_gradient: float, commit=True): """ calculate gradients in percents based on altitude and distance while cleaning up bad values. """ data = self.data.copy() data["geom"], srid = self.geom, self.geom.srid # calculate gradients data["gradient"] = data.altitude.diff() / data.distance.diff() * 100 # find rows with offending gradients bad_rows = data[(data["gradient"] < -max_gradient) | (data["gradient"] > max_gradient)] # drop bad rows and recalculate until all offending values have been removed while not bad_rows.empty: data.drop(bad_rows.index, inplace=True) data["gradient"] = data.altitude.diff() / data.distance.diff( ) * 100 bad_rows = data[(data["gradient"] < -max_gradient) | (data["gradient"] > max_gradient)] # save the values back to the track object try: self.geom = LineString(data.geom.tolist(), srid=srid) except ValueError: message = "Cannot clean track data: invalid altitude values." logger.error(message, exc_info=True) raise ValueError(message) data.drop(columns=["geom"], inplace=True) self.data = data.fillna(value=0) if commit: self.save(update_fields=["data", "geom"]) def calculate_cumulative_elevation_differences(self, commit=True): """ Calculates two columns from the altitude data: - cumulative_elevation_gain: cumulative sum of positive elevation data - cumulative_elevation_loss: cumulative sum of negative elevation data """ # only consider entries where altitude difference is greater than 0 self.data["cumulative_elevation_gain"] = self.data.altitude.diff()[ self.data.altitude.diff() >= 0].cumsum() # only consider entries where altitude difference is less than 0 self.data["cumulative_elevation_loss"] = self.data.altitude.diff()[ self.data.altitude.diff() <= 0].cumsum() # Fill the NaNs with the last valid value of the series # then, replace the remaining NaN (at the beginning) with 0 self.data[["cumulative_elevation_gain", "cumulative_elevation_loss"]] = (self.data[[ "cumulative_elevation_gain", "cumulative_elevation_loss" ]].fillna(method="ffill").fillna(value=0)) if commit: self.save(update_fields=["data"]) def add_distance_and_elevation_totals(self, commit=True): """ add total distance and total elevation gain to every row """ self.data["total_distance"] = self.total_distance self.data["total_elevation_gain"] = self.total_elevation_gain if commit: self.save(update_fields=["data"]) def update_permanent_track_data(self, min_step_distance=1, max_gradient=100, commit=True, force=False): """ make sure all unvarying data columns required for schedule calculation are available. :param min_step_distance: minimum distance in m to keep between each point :param max_gradient: maximum gradient to keep when cleaning rows :param commit: save the instance to the database after update :param force: recalculates columns even if they are already present :returns: None :raises ValueError: if the number of coords in the track geometry is not equal to the number of rows in data or if the cleaned data columns are left with only one row. """ # flag if any of the data columns have been updated track_data_updated = False # make sure we have step distances if "step_distance" not in self.data.columns or force: track_data_updated = True self.calculate_step_distances(min_distance=min_step_distance, commit=False) # make sure we have step gradients if "gradient" not in self.data.columns or force: track_data_updated = True self.calculate_gradients(max_gradient=max_gradient, commit=False) # make sure we have cumulative elevation differences if (not all(column in self.data.columns for column in ["cumulative_elevation_gain", "cumulative_elevation_loss"]) or force): track_data_updated = True self.calculate_cumulative_elevation_differences(commit=False) # make sure we have distance and elevation totals if (not all(column in self.data.columns for column in ["total_distance", "total_elevation_gain"]) or force): track_data_updated = True self.add_distance_and_elevation_totals(commit=False) # commit changes to the database if any if track_data_updated and commit: self.save(update_fields=["data", "geom"]) def update_track_details_from_data(self, commit=True): """ set track details from the track data, usually replacing remote information received for the route """ if not all(column in ["cumulative_elevation_gain", "cumulative_elevation_loss"] for column in self.data.columns): self.calculate_cumulative_elevation_differences(commit=False) # update total_distance, total_elevation_gain and total_elevation_loss from data self.total_distance = self.data.distance.max() self.total_elevation_loss = abs( self.data.cumulative_elevation_loss.min()) self.total_elevation_gain = self.data.cumulative_elevation_gain.max() if commit: self.save(update_fields=[ "total_distance", "total_elevation_loss", "total_elevation_gain", ]) def get_prediction_model(self, user): """ get the prediction model from the Model instance containing prediction values Use an instance of ActivityPerformance if it exists for the athlete and activity type. Fallback on ActivityType otherwise. """ if user.is_authenticated: try: performance = user.athlete.performances performance = performance.filter( activity_type=self.activity_type).get() return performance.get_prediction_model() except ActivityPerformance.DoesNotExist: pass # no ActivityPerformance for the user, fallback on ActivityType return self.activity_type.get_prediction_model() def calculate_projected_time_schedule(self, user, workout_type=None, gear=None): """ Calculates route pace and route schedule based on the athlete's prediction model for the route's activity type. """ # make sure we have all required data columns self.update_permanent_track_data() # add temporary columns useful to the schedule calculation data = self.data # add gear and workout type to every row data["gear"] = gear or "None" data["workout_type"] = workout_type or "None" # restore prediction model for athlete and activity_type prediction_model = self.get_prediction_model(user) # keep model pipelines and columns in local variable for readability pipeline = prediction_model.pipeline numerical_columns = prediction_model.numerical_columns categorical_columns = prediction_model.categorical_columns feature_columns = numerical_columns + categorical_columns # calculate pace and schedule columns for the route data["pace"] = pipeline.predict(data[feature_columns]) data["schedule"] = (data.pace * data.step_distance).cumsum().fillna(value=0) self.data = data def get_data(self, line_location, data_column): """ interpolate the value of a given column in the DataFrame based on the line_location and the total_distance column. """ # calculate the distance value to interpolate with # based on line location and the total length of the track. interp_x = line_location * self.total_distance # interpolate the value, see: # https://docs.scipy.org/doc/numpy/reference/generated/numpy.interp.html return interp(interp_x, self.data["distance"], self.data[data_column]) def get_distance_data(self, line_location, data_column, absolute=False): """ wrap around the get_data method to return a Distance object. """ distance_data = self.get_data(line_location, data_column) # return distance object if distance_data is not None: return D(m=abs(distance_data)) if absolute else D(m=distance_data) def get_time_data(self, line_location, data_column): """ wrap around the get_data method to return a timedelta object. """ time_data = self.get_data(line_location, data_column) # return time object if time_data is not None: return timedelta(seconds=int(time_data)) def get_start_altitude(self): start_altitude = self.get_distance_data(0, "altitude") return start_altitude def get_end_altitude(self): end_altitude = self.get_distance_data(1, "altitude") return end_altitude def get_total_distance(self): """ returns track total_distance as a Distance object """ return D(m=self.total_distance) def get_total_elevation_gain(self): """ returns total altitude gain as a Distance object """ return D(m=self.total_elevation_gain) def get_total_elevation_loss(self): """ returns total altitude loss as a Distance object """ return D(m=self.total_elevation_loss) def get_total_duration(self): """ returns total duration as a timedelta object """ return self.get_time_data(1, "schedule") def get_closest_places_along_line(self, line_location=0, max_distance=200): """ retrieve Place objects with a given distance of a point on the line. """ # create the point from location point = self.geom.interpolate_normalized(line_location) # get closest places to the point places = get_places_within(point, max_distance) return places def get_start_places(self, max_distance=200): """ retrieve Place objects close to the start of the track. """ return self.get_closest_places_along_line(line_location=0, max_distance=max_distance) def get_end_places(self, max_distance=200): """ retrieve Place objects close to the end of the track. """ return self.get_closest_places_along_line(line_location=1, max_distance=max_distance)
class UUIDModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
def make_django_model(meta_model, divider): class Meta: verbose_name = _(meta_model.get_verbose_name()) verbose_name_plural = _(meta_model.get_verbose_name_plural()) ordering = ('-modification_date', '-creation_date') unique_together = tuple( meta_model.get_property(prop_name='m_unique_together', default_value=[])) modelisation_version = getattr(meta_model, 'version', None) if (modelisation_version is not None and modelisation_version not in HANDELED_MODELISATION_VERSIONS): raise ValueError('Unknown modelisation format') creation_level = get_minimum_level( meta_model=meta_model, prop_name='m_creation_minimum_level', default_value='authenticated', ) retrieve_level = get_minimum_level( meta_model=meta_model, prop_name='m_retrieve_minimum_level', default_value='authenticated', ) update_level = get_minimum_level( meta_model=meta_model, prop_name='m_update_minimum_level', default_value='authenticated', ) delete_level = get_minimum_level( meta_model=meta_model, prop_name='m_delete_minimum_level', default_value='superuser', ) attrs = { 'Meta': Meta, '__module__': 'concrete_datastore.concrete.models', '__str__': make_unicode_method(meta_model), '__creation_minimum_level__': creation_level, '__retrieve_minimum_level__': retrieve_level, '__update_minimum_level__': update_level, '__delete_minimum_level__': delete_level, } is_default_public = meta_model.get_property( prop_name='m_is_default_public', default_value=False) if is_default_public is None or is_default_public not in (True, False): is_default_public = False attrs.update(get_common_fields(public_default_value=is_default_public)) ancestors = (models.Model, ) if meta_model.get_model_name() == 'User': ancestors = ( AbstractUser, HasPermissionAbstractUser, ConfirmableUserAbstract, ) attrs.update({ 'username': None, 'objects': UserManager(), 'admin': models.BooleanField(default=False), 'password_modification_date': models.DateField(default=date.today), 'subscription_notification_token': models.UUIDField(default=uuid.uuid4, editable=False), 'login_counter': models.IntegerField(default=0), 'external_auth': models.BooleanField(default=False), # True if user created by an external auth 'USERNAME_FIELD': 'email', 'REQUIRED_FIELDS': [], }) else: attrs.update(get_user_tracked_fields()) for field_name, field in meta_model.get_fields(): if field_name in attrs: raise ValueError( f'{field_name} is a protected field and cannot be overwritten') # obsoletes fields if meta_model.get_model_name() == 'User': obsoletes_level = ('guest', 'manager', 'blocked') if field_name in obsoletes_level: raise ValueError( f'The fields {obsoletes_level} are no longer supported') args = field.f_args if field.f_type == 'FileField': args.update({ 'blank': True, 'null': True, 'validators': [validate_file] }) elif field.f_type == 'JSONField': json_args = args json_args['blank'] = True json_args['encoder'] = None json_args['null'] = False json_args['default'] = dict attrs.update({field_name: JSONField(**json_args)}) continue elif field.f_type in ('CharField', 'TextField'): args['null'] = False args.setdefault('blank', True) args.setdefault('default', "") elif field.f_type in ('IntegerField', 'BigIntegerField'): args['null'] = False args.setdefault('blank', True) args.setdefault('default', 0) elif field.f_type == 'DecimalField': args['null'] = False args.setdefault('decimal_places', 2) args.setdefault('max_digits', 20) args.setdefault('default', 0.00) elif field.f_type in ('ForeignKey', ): # Copy args to not alter the real field.f_args args = args.copy() # Force FK to null=True to avoid default value problems args.update({'null': True}) # PROTECT foreign key deletion by default on_delete_rule = args.get('on_delete', 'PROTECT') if on_delete_rule not in ('CASCADE', 'SET_NULL', 'PROTECT'): raise ValueError( f'On delete rule "{on_delete_rule}" is invalid. ' 'It must be "CASCADE", "SET_NULL" or "PROTECT"') args.update({'on_delete': getattr(models, on_delete_rule)}) elif field.f_type in ('GenericIPAddressField', ): #: If blank is True, null should be too #: https://docs.djangoproject.com/fr/3.1/ref/models/fields/#genericipaddressfield if args.get('blank', False) is True: args['null'] = True else: args.setdefault('blank', False) args['null'] = False elif field.f_type in ('DateTimeField', ): if args.get('null', False) is True: args['null'] = True args['blank'] = True else: args['default'] = timezone.now args['null'] = False args.setdefault('blank', False) elif field.f_type in ('DateField', ): if args.get('null', False) is True: args['null'] = True args['blank'] = True else: args['default'] = date.today args['null'] = False args.setdefault('blank', False) elif field.f_type == 'ManyToManyField': # Copy args to not alter the real field.f_args args = args.copy() args.pop('null', None) attrs.update({field_name: getattr(models, field.f_type)(**args)}) if meta_model.get_model_name() != divider: if meta_model.get_model_name() == "User": attrs.update(get_divider_fields_manytomany(divider)) attrs.update(get_divider_notification_fields(divider)) attrs.update( {'email': models.EmailField(_('email address'), unique=True)}) elif meta_model.get_model_name() in UNDIVIDED_MODEL: pass else: attrs.update(get_divider_fields_foreignkey(divider)) return type(meta_model.get_model_name(), ancestors, attrs)
class GeoService(models.Model): service_type = 'generic' def save(self, *args, **kwargs): if self.service_type == GeoService.service_type: raise Exception('Base geo service model can\'t be saved') self.type = self.service_type self.extent = self.boundary.envelope if self.boundary else None self.boundary_area = self.boundary.area if self.boundary else None super(GeoService, self).save(*args, **kwargs) guid = models.UUIDField(_('service guid'), default=uuid.uuid4, editable=False) name = models.CharField(_('service name'), unique=True, max_length=100, blank=False, null=False) desc = models.TextField(_('description'), blank=True, null=True) type = models.CharField(_('service type'), max_length=20, editable=False, null=False) epsg = models.IntegerField(_('EPSG Code'), null=True, blank=True) icon = models.ForeignKey(ServiceIcon, models.SET_NULL, blank=True, null=True) # license license_name = models.CharField(_('license name'), max_length=256, blank=True, null=True) license_url = models.URLField(_('license url'), max_length=512, blank=True, null=True) copyright_text = models.CharField(_('copyright text'), max_length=2048, blank=True, null=True) copyright_url = models.URLField(_('copyright url'), max_length=512, blank=True, null=True) terms_of_use_url = models.URLField(_('terms of use url'), max_length=512, blank=True, null=True) # creation & update info submitter = models.ForeignKey(NextgisUser, on_delete=models.SET_NULL, to_field='nextgis_guid', null=True) created_at = models.DateTimeField(_('created at'), auto_now_add=True) updated_at = models.DateTimeField(_('updated at'), null=True, blank=True) # source info source = models.TextField(_('source'), blank=True, null=True) source_url = models.URLField(_('source url'), max_length=512, blank=True, null=True) # status last_status = models.ForeignKey('GeoServiceStatus', on_delete=models.SET_NULL, null=True, related_name='last_for', blank=True) # extent & boundary extent = models.PolygonField(srid=4326, spatial_index=True, null=True, blank=True) boundary = models.MultiPolygonField(srid=4326, spatial_index=True, null=True, blank=True) boundary_area = models.FloatField(null=True, blank=True) # TODO: tags def __str__(self): return self.name def get_typed_instance(self): if self.type == TmsService.service_type: return self.tmsservice if self.type == WmsService.service_type: return self.wmsservice if self.type == WfsService.service_type: return self.wfsservice if self.type == GeoJsonService.service_type: return self.geojsonservice return self
class NextgisUser(AbstractBaseUser, PermissionsMixin): """ Полная копия стандартного User + дополнительные поля: nextgis_guid """ username = models.CharField( _('username'), max_length=30, unique=True, help_text= _('Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.' ), validators=[ validators.RegexValidator( r'^[\w.@+-]+$', _('Enter a valid username. This value may contain only ' 'letters, numbers ' 'and @/./+/-/_ characters.')), ], error_messages={ 'unique': _("A user with that username already exists."), }, ) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=30, blank=True) email = models.EmailField(_('email address'), blank=True) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_( 'Designates whether the user can log into this admin site.'), ) is_active = models.BooleanField( _('active'), default=True, help_text=_( 'Designates whether this user should be treated as active. ' 'Unselect this instead of deleting accounts.'), ) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) # custom fields nextgis_guid = models.UUIDField(_('nextgis guid'), default=uuid.uuid4, editable=False, unique=True) locale = models.CharField(_('user locale'), max_length=30, null=True, blank=False, choices=SupportedLanguages.dict_text.items(), default=SupportedLanguages.DEFAULT) email_confirmed = models.BooleanField(_('mail confirmed'), default=False) @property def nextgis_id(self): return self.id objects = NextgisUserManager() USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] class Meta: verbose_name = _('user') verbose_name_plural = _('users') def get_full_name(self): """ Returns the first_name plus the last_name, with a space in between. """ full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip() def get_short_name(self): "Returns the short name for the user." return self.first_name def email_user(self, subject, message, from_email=None, **kwargs): """ Sends an email to this User. """ send_mail(subject, message, from_email, [self.email], **kwargs)
class UserResponse(models.Model): uuid = models.UUIDField(db_index=True, default=uuid_lib.uuid4, editable=False) date_created = models.DateTimeField(default=timezone.now, blank=True) question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="response_question") option = models.ForeignKey(QuestionOption, on_delete=models.CASCADE, related_name="response_option") response_set = models.ForeignKey(UserResponseSet, on_delete=models.CASCADE, related_name="response_response_set")
class UserResponseSet(models.Model): uuid = models.UUIDField(db_index=True, default=uuid_lib.uuid4, editable=False) date_created = models.DateTimeField(default=timezone.now, blank=True) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="response_set_user")
class Guidebook(models.Model): unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) user = models.ForeignKey(UserModel, on_delete=models.CASCADE) name = models.CharField(max_length=100) description = models.TextField(null=True) category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True) cover_image = models.ImageField(upload_to=image_directory_path, null=True) tag = models.ManyToManyField(Tag) is_published = models.BooleanField(default=False) is_approved = models.BooleanField(default=True) created_at = models.DateTimeField(default=datetime.now, blank=True) updated_at = models.DateTimeField(default=datetime.now, blank=True) def get_absolute_url(self): return reverse('guidebook.guidebook_detail', kwargs={'unique_id': str(self.unique_id)}) def getScenes(self): scenes = Scene.objects.filter(guidebook=self).order_by('sort') return scenes def getScenePositions(self): scenes = Scene.objects.filter(guidebook=self) positions = [] if scenes and scenes.count() > 0: for scene in scenes: position = [scene.lat, scene.lng] positions.append(position) return positions def getFirstScene(self): scenes = Scene.objects.filter(guidebook=self).order_by('sort') if scenes and scenes.count() > 0: firstScene = scenes[0] return firstScene else: return '' def getSceneCount(self): scenes = Scene.objects.filter(guidebook=self) return scenes.count() def getLikeCount(self): liked_guidebook = GuidebookLike.objects.filter(guidebook=self) if not liked_guidebook: return 0 else: return liked_guidebook.count() def getShortDescription(self): description = self.description if len(description) > 100: return description[0:100] + '...' else: return description def getTagStr(self): tags = [] if self.tag is None: return '' for tag in self.tag.all(): if tag and tag.is_actived: tags.append(tag.name) if len(tags) > 0: return ', '.join(tags) else: return '' def getTags(self): tags = [] if self.tag is None: return [] for tag in self.tag.all(): if tag and tag.is_actived: tags.append(tag.name) return tags def getCoverImage(self): scenes = Scene.objects.filter(guidebook=self) if scenes.count() > 0: return scenes[0].image_key else: return None
class Profile(BaseModel): MALE = 'm' FEMALE = 'f' OTHER = 'o' SEX_CHOICES = ((MALE, _('male')), (FEMALE, _('female')), (OTHER, _('other'))) RACING_CYCLE = 'racing_cycle' CITY_BIKE = 'city_bike' MOUNTAIN_BIKE = 'mountain_bike' E_BIKE = 'e_bike' CARGO_BIKE = 'cargo_bike' E_CARGO_BIKE = 'e_cargo_bike' CATEGORY_OF_BIKE_CHOICES = ( (RACING_CYCLE, _('racing cycle')), (CITY_BIKE, _('city bike')), (MOUNTAIN_BIKE, _('mountain bike')), (E_BIKE, _('e-bike')), (CARGO_BIKE, _('cargo bike')), (E_CARGO_BIKE, _('e-cargo-bike')), ) NEVER = 0 ONCE_PER_MONTH = 1 ONCE_PER_WEEK = 2 ONCE_PER_DAY = 3 USAGE_CHOICES = ( (NEVER, _('never')), (ONCE_PER_DAY, _('once per day')), (ONCE_PER_WEEK, _('once per week')), (ONCE_PER_MONTH, _('once per month')), ) age = models.PositiveSmallIntegerField(_('age'), blank=True, null=True) category_of_bike = models.CharField( _('category of bike'), blank=True, null=True, max_length=20, choices=CATEGORY_OF_BIKE_CHOICES, ) has_trailer = models.BooleanField(_('has trailer'), blank=True, null=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) postal_code = models.CharField(_('postal code'), blank=True, null=True, max_length=5) sex = models.CharField(_('sex'), blank=True, null=True, max_length=1, choices=SEX_CHOICES) speed = models.PositiveSmallIntegerField(_('speed'), blank=True, null=True) security = models.PositiveSmallIntegerField(_('security'), blank=True, null=True) usage = models.PositiveSmallIntegerField(_('usage'), blank=True, null=True, choices=USAGE_CHOICES) class Meta: verbose_name = _('profile') verbose_name_plural = _('profiles')
class RegistriesApplication(AuditModel): """ Application from a well driller or pump installer to be on the GWELLS Register. """ application_guid = models.UUIDField( primary_key=True, default=uuid.uuid4, editable=False, verbose_name='Register Application UUID', db_comment='Unique identifier for the registries_application record.') registration = models.ForeignKey( Register, db_column='register_guid', on_delete=models.PROTECT, verbose_name='Person Reference', related_name='applications', db_comment='Unique identifier for the registries_register record.') subactivity = models.ForeignKey(SubactivityCode, db_column='registries_subactivity_code', on_delete=models.PROTECT, related_name="applications") file_no = models.CharField( max_length=25, blank=True, null=True, verbose_name='ORCS File # reference.', db_comment= ('Operational Records Classification Systems (ORCS) number. Information schedules used ' 'to classify, file, retrieve and dispose of operational records. This number is ' 'assigned on creation of a file.')) proof_of_age = models.ForeignKey(ProofOfAgeCode, db_column='registries_proof_of_age_code', on_delete=models.PROTECT, verbose_name="Proof of age.", null=False) registrar_notes = models.CharField( max_length=255, blank=True, null=True, verbose_name='Registrar notes, for internal use only.', db_comment= 'Internal notes documenting communication between an applicant and the province.' ) reason_denied = models.CharField( max_length=255, blank=True, null=True, verbose_name='Free form text explaining reason for denial.', db_comment= ('The reason the Comptroller did not approve an individuals application for well driller ' 'or well pump installer, for example not meeting the requirements of the application. ' 'A brief internal note.')) # TODO Support multiple certificates primary_certificate = models.ForeignKey(AccreditedCertificateCode, blank=True, null=True, db_column='acc_cert_guid', on_delete=models.PROTECT, verbose_name="Certificate") primary_certificate_no = models.CharField( max_length=50, db_comment= 'Unique number assigned to the certificate by the certifying organization.' ) @property def display_status(self): # When an application is removed, it's status remains "Active", and only the removal date is # populated. We spare the front-end from having to know about this, by generating a human # readable property on this level. status = None if self.removal_date: status = 'Removed' elif self.current_status: status = self.current_status.description return status # TODO Should probably force this to have a default value of Pending! # This field should really be called "Approval Outcome" current_status = models.ForeignKey( ApplicationStatusCode, blank=True, null=True, db_column='registries_application_status_code', on_delete=models.PROTECT, verbose_name="Application Status Code Reference") application_recieved_date = models.DateField( blank=True, null=True, db_comment= ('Date that the province received an application for registration of a well driller or ' 'well pump installer.')) application_outcome_date = models.DateField( blank=True, null=True, db_comment= ('Date that the comptroller decided if the application for registration of a well ' 'driller or well pump installer was approved or denied.')) application_outcome_notification_date = models.DateField( blank=True, null=True, db_comment= ('Date that the individual was notified of the outcome of their application for ' 'registration for well driller or well pump installer.')) # The "removal_date" refers to the date on which a classification is "removed" from the register. # Removing a classification may result in a person being removed from the public register as a whole, # only if there are no other Approved classification. removal_date = models.DateField( blank=True, null=True, db_comment= 'Date that a registered individual was removed from the register.') removal_reason = models.ForeignKey( RegistriesRemovalReason, db_column='registries_removal_reason_code', on_delete=models.PROTECT, blank=True, null=True, verbose_name='Removal Reason') history = GenericRelation(Version) class Meta: db_table = 'registries_application' verbose_name_plural = 'Applications' ordering = ['primary_certificate_no'] db_table_comment = 'Placeholder table comment.' def __str__(self): return '%s : %s' % (self.registration, self.file_no)
class TripTemplate(models.Model): """Trip and template objects. A template records a plan for an excursion. It may have many points of interest and routes associated with it, and other documents and file. A trip plans for and records an event. It may be cloned from a template, or created blank. A trip record will have a range of dates associated with it. GPX files can be uploaded to trip records, and any waypoints, tracks our rutes in that file injected into the datbase. The identifier should be a universal unique identifier import uuid uuid.uuid4().hex Should yield something like this 32 character string: 70d0209ecd564104936dc2c09cfaeabe """ TRIP_TYPE = ( ('air', 'air'), ('boat', 'boat'), ('cycle', 'cycle'), ('road', 'road'), ('tramping', 'tramping'), ) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) trip_type = models.CharField(max_length=64, choices=TRIP_TYPE, default='tramping') name = models.CharField(max_length=255) subject = models.CharField(max_length=255, blank=True, null=True) description = models.TextField(blank=True, null=True) location = models.CharField(max_length=255, blank=True, null=True) days_length = models.IntegerField(default=1) owner = models.CharField(max_length=255, blank=True, null=True) group = models.CharField(max_length=255, blank=True, null=True) class Meta: abstract = True def __unicode__(self): return self.name def __get_absolute_url__(self): return os.path.join(settings.BASE_URL, self.identifier()) url = property(__get_absolute_url__) def waypoints(self): return Waypoint.objects.filter(trip=self) def directory(self): """Return a webnote.Directory object of the filespace.""" filespace = self.filespace() if not os.path.isdir(filespace): self.make_filespace() directory = webnote.Directory(filespace) return directory def gpxfiles(self): """A list of parsed gpx file objects.""" gpxfiles = [] for gpxfile in self.directory().model['gpx']: filepath = os.path.join(self.filespace(), gpxfile) if os.path.isfile(filepath): f = open(filepath) gpxf = GPXFile(f, self) gpxfiles.append(gpxf.analyse()) return gpxfiles def save(self, files=None, *args, **kwargs): """Save any uploaded file to filespace.""" if not os.path.isdir(self.filespace()): self.make_filespace() if files: for item in files: f = files[item] filepath = os.path.join(self.filespace(), str(f)) with open(filepath, 'wb+') as destination: for chunk in f.chunks(): destination.write(chunk) super(TripTemplate, self).save(*args, **kwargs) def identifier(self): """The first eight characters of the uuid, chopped by hyphen.""" uri_steps = str(self.id).split('-') return uri_steps[0] def filespace(self): """Return a string pathname to this object's filespace in static files. """ dirname = self.name.replace(' ', '-').replace("'", "") #dirname = dirname + '_' + self.identifier() filepath = os.path.join(settings.STATICFILES_DIR, settings.BASE_FILESPACE, dirname) return filepath def make_filespace(self): if not os.path.isdir(self.filespace()): os.mkdir(self.filespace()) return True return False
class Location(models.Model): TYPE_CHOICES = [ ('line', _('line')), ('area', _('area')), ] SUBDIVISION_CHOICES = [ ('north', _('north')), ('northeast', _('northeast')), ('east', _('east')), ('southeast', _('southeast')), ('south', _('south')), ('southwest', _('southwest')), ('west', _('west')), ('northwest', _('northwest')), ('center', _('center')), ] CHARACTER_CHOICES = [ ('commercial', _("Commercial")), ('cbd', _("Central business district")), ('civic', _("Civic")), ('cultural', _("Cultural")), ('educational', _("Educational")), ('industrial', _("Industrial")), ('infrastructural', _("Infrastructural")), ('medical', _("Medical")), ('mixed', _("Mixed")), ('office', _("Office")), ('recreational', _("Recreational")), ('residential', _("Residential")), ('rural', _("Rural")), ('stadium', _("Stadium")), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) agency = models.ForeignKey(Agency, on_delete=models.CASCADE) country = models.ForeignKey(Country, on_delete=models.CASCADE, help_text=_("Country that the survey location " "is based within.")) region = models.CharField(max_length=1000, blank=True, help_text=_("State, county, or municipal " "boundary of the location.")) city = models.CharField(max_length=1000, blank=True, help_text=_("Name of the city that the survey " "location is based within. Leave " "blank if the survey location is not " "based within a city.")) name_primary = models.CharField(max_length=1000, help_text=_("Official, specific name of " "the survey location.")) name_secondary = models.CharField(max_length=1000, blank=True, help_text=_("Secondary or specifying " "name of the survey " "location. Leave blank if " "no specification is " "necessary.")) subdivision = models.CharField(max_length=9, choices=SUBDIVISION_CHOICES, blank=True, help_text=_("Indication of whether the " "location is a subdivision of " "a single survey location.")) character = models.CharField(max_length=15, blank=True, choices=CHARACTER_CHOICES, help_text=_("Primary character of the survey " "location's immediate " "surroundings.")) geometry = models.GeometryField(help_text=_("Polygon or line that " "describes the geometry " "of the location. Polygons " "are intended for counts of " "people staying in an area " "while lines are intended for " "counts of people moving " "across a threshold.")) geometry_type = models.CharField(max_length=4, choices=TYPE_CHOICES, blank=True, help_text=_("Indication of whether the " "location is intended for " "counts of people " "moving (across a line), " "or whether " "it is intended for counts " "of people " "staying (within an area).")) is_active = models.BooleanField(default=True) def __str__(self): return self.name_primary
class RecorridoProposed(models.Model): recorrido = models.ForeignKey('core.Recorrido') parent = models.UUIDField(default=uuid.uuid4) uuid = models.UUIDField(default=uuid.uuid4) nombre = models.CharField(max_length=100) linea = models.ForeignKey('core.Linea') ruta = models.LineStringField() sentido = models.CharField(max_length=100, blank=True, null=True) slug = models.SlugField(max_length=200, blank=True, null=True) inicio = models.CharField(max_length=100, blank=True, null=True) fin = models.CharField(max_length=100, blank=True, null=True) semirrapido = models.BooleanField(default=False) color_polilinea = models.CharField(max_length=20, blank=True, null=True) horarios = models.TextField(blank=True, null=True) pois = models.TextField(blank=True, null=True) descripcion = models.TextField(blank=True, null=True) current_status = models.CharField(max_length=1, choices=MODERATION_CHOICES, default='E') date_create = models.DateTimeField(auto_now_add=True) date_update = models.DateTimeField(auto_now=True) # Si tiene las paradas completas es porque tiene todas las paradas de # este recorrido en la tabla paradas+horarios (horarios puede ser null), # y se puede utilizar en la busqueda usando solo las paradas. paradas_completas = models.BooleanField(default=False) @property def ciudades(self): return Ciudad.objects.filter(lineas=self.linea) objects = models.GeoManager() def save(self, *args, **kwargs): user = kwargs.pop('user', None) super(RecorridoProposed, self).save(*args, **kwargs) mod = self.get_moderacion_last() if mod is None or (user is not None and user != mod.created_by): self.logmoderacion_set.create(created_by=user) def get_current_status_display(self): status_list = self.logmoderacion_set.all().order_by('-date_create') if status_list: return status_list[0].get_newStatus_display() else: return None def log(self): return self.logmoderacion_set.all().order_by('-date_create') def get_moderacion_last(self): loglist = self.logmoderacion_set.all().order_by('-date_create') if loglist: return loglist[0] else: return None def get_moderacion_last_user(self): loglist = self.logmoderacion_set.filter( created_by__is_staff=False).order_by('-date_create') if loglist: return loglist[0].created_by else: return AnonymousUser() def get_pretty_user(self): user = self.get_moderacion_last_user() if user.is_anonymous(): return "Usuario Anónimo" else: if user.first_name or user.last_name: return user.first_name + " " + user.last_name else: return user.username def get_fb_uid(self): last = self.get_moderacion_last_user() if last != AnonymousUser(): return last.social_auth.get(provider='facebook').uid else: return None def __unicode__(self): return str(self.linea) + " - " + self.nombre def aprobar(self, user): r = self.recorrido if not r.uuid: # todavia no existe una version de este recorrido real, que estoy por retirar # antes de retirarlo creo su version correspondiente rp = RecorridoProposed(recorrido=r, nombre=r.nombre, linea=r.linea, ruta=r.ruta, sentido=r.sentido, slug=r.slug, inicio=r.inicio, fin=r.fin, semirrapido=r.semirrapido, color_polilinea=r.color_polilinea, pois=r.pois, descripcion=r.descripcion) rp.save(user=user) self.parent = rp.uuid self.save() r.recorrido = self.recorrido r.nombre = self.nombre r.linea = self.linea r.ruta = self.ruta r.sentido = self.sentido r.inicio = self.inicio r.fin = self.fin r.semirrapido = self.semirrapido r.color_polilinea = self.color_polilinea r.pois = self.pois r.descripcion = self.descripcion r.save() try: parent = RecorridoProposed.objects.get(uuid=self.parent) if parent: parent.logmoderacion_set.create(created_by=user, newStatus='R') except RecorridoProposed.DoesNotExist: pass for rp in RecorridoProposed.objects.filter( current_status='S', recorrido=r.recorrido).exclude(uuid=self.uuid): rp.logmoderacion_set.create(created_by=user, newStatus='R') self.logmoderacion_set.create(created_by=user, newStatus='S') #call_command('crear_thumbs', recorrido_id=self.recorrido.id) # Notificacion por facebook token = urllib2.urlopen( 'https://graph.facebook.com/oauth/access_token?client_id=' + settings.FACEBOOK_APP_ID + '&client_secret=' + settings.FACEBOOK_API_SECRET + '&grant_type=client_credentials').read().split('access_token=')[1] user = self.get_moderacion_last_user() if not user.is_anonymous(): fb = user.social_auth.filter(provider='facebook') if len(fb) != 0: from facebook import GraphAPI fb_uid = fb[0].uid graph = GraphAPI(token) graph.request( "/" + fb_uid + "/notifications/", post_args={ "template": 'Felicitaciones! Un moderador aceptó tu edición en cualbondi', "href": "https://cualbondi.com.ar/revision/" + str(self.id) + "/" }) def get_absolute_url(self): url = reverse('revision_externa', kwargs={ 'id_revision': self.id, }) print "URL: " + url return url class Meta: permissions = (("moderate_recorridos", "Can moderate (accept/decline) recorridos"), )
class Job(models.Model): """ Database model for an 'Export'. Immutable, except in the case of HDX Export Regions. """ id = models.AutoField(primary_key=True, editable=False) uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False, db_index=True) user = models.ForeignKey(User, related_name='owner') name = models.CharField(max_length=100, db_index=True, blank=False) description = models.CharField(max_length=1000, db_index=True, default='', blank=True) event = models.CharField(max_length=100, db_index=True, default='', blank=True) export_formats = ArrayField(models.CharField(max_length=10), validators=[validate_export_formats], blank=False) published = models.BooleanField(default=False, db_index=True) the_geom = models.GeometryField(verbose_name='Uploaded geometry', srid=4326, blank=False) simplified_geom = models.GeometryField(verbose_name='Simplified geometry', srid=4326, blank=True, null=True) objects = models.GeoManager() feature_selection = models.TextField( blank=False, validators=[validate_feature_selection]) created_at = models.DateTimeField(default=timezone.now, editable=False) updated_at = models.DateTimeField(default=timezone.now, editable=False) mbtiles_maxzoom = models.IntegerField(null=True, blank=True) mbtiles_minzoom = models.IntegerField(null=True, blank=True) mbtiles_source = models.TextField(null=True, blank=True) # flags buffer_aoi = models.BooleanField(default=False) unlimited_extent = models.BooleanField(default=False) hidden = models.BooleanField(default=False) # hidden from the list page expire_old_runs = models.BooleanField(default=True) pinned = models.BooleanField(default=False) unfiltered = models.BooleanField(default=False) class Meta: # pragma: no cover managed = True db_table = 'jobs' @property def osma_link(self): bounds = self.the_geom.extent return "http://osm-analytics.org/#/show/bbox:{0},{1},{2},{3}/buildings/recency".format( *bounds) @property def area(self): return get_geodesic_area(self.the_geom) def save(self, *args, **kwargs): self.the_geom = force2d(self.the_geom) self.simplified_geom = simplify_geom(self.the_geom, force_buffer=self.buffer_aoi) super(Job, self).save(*args, **kwargs) def __str__(self): return str(self.uid)
class DefaultDivider(models.Model): uid = models.UUIDField(primary_key=True, default=uuid.uuid4) name = models.CharField(max_length=250, default="Default Divider")
class ResourceXResource(models.Model): resourcexid = models.UUIDField(primary_key=True, default=uuid.uuid1) # This field type is a guess. resourceinstanceidfrom = models.ForeignKey( "ResourceInstance", db_column="resourceinstanceidfrom", blank=True, null=True, related_name="resxres_resource_instance_ids_from", on_delete=models.CASCADE, db_constraint=False, ) resourceinstanceidto = models.ForeignKey( "ResourceInstance", db_column="resourceinstanceidto", blank=True, null=True, related_name="resxres_resource_instance_ids_to", on_delete=models.CASCADE, db_constraint=False, ) notes = models.TextField(blank=True, null=True) relationshiptype = models.TextField(blank=True, null=True) inverserelationshiptype = models.TextField(blank=True, null=True) tileid = models.ForeignKey( "TileModel", db_column="tileid", blank=True, null=True, related_name="resxres_tile_id", on_delete=models.CASCADE, ) nodeid = models.ForeignKey("Node", db_column="nodeid", blank=True, null=True, related_name="resxres_node_id", on_delete=models.CASCADE,) datestarted = models.DateField(blank=True, null=True) dateended = models.DateField(blank=True, null=True) created = models.DateTimeField() modified = models.DateTimeField() def delete(self, *args, **kwargs): from arches.app.search.search_engine_factory import SearchEngineInstance as se from arches.app.search.mappings import RESOURCE_RELATIONS_INDEX se.delete(index=RESOURCE_RELATIONS_INDEX, id=self.resourcexid) # update the resource-instance tile by removing any references to a deleted resource deletedResourceId = kwargs.pop("deletedResourceId", None) if deletedResourceId and self.tileid and self.nodeid: newTileData = [] data = self.tileid.data[str(self.nodeid_id)] if type(data) != list: data = [data] for relatedresourceItem in data: if relatedresourceItem["resourceId"] != str(deletedResourceId): newTileData.append(relatedresourceItem) self.tileid.data[str(self.nodeid_id)] = newTileData self.tileid.save() super(ResourceXResource, self).delete() def save(self): from arches.app.search.search_engine_factory import SearchEngineInstance as se from arches.app.search.mappings import RESOURCE_RELATIONS_INDEX if not self.created: self.created = datetime.datetime.now() self.modified = datetime.datetime.now() document = model_to_dict(self) se.index_data(index=RESOURCE_RELATIONS_INDEX, body=document, idfield="resourcexid") super(ResourceXResource, self).save() class Meta: managed = True db_table = "resource_x_resource"
class EmailDevice(Device): """ A :class:`~django_otp.models.Device` that delivers a token to the user's registered email address (``user.email``). This is intended for demonstration purposes; if you allow users to reset their passwords via email, then this provides no security benefits. .. attribute:: key *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20 random bytes) """ uid = models.UUIDField(default=uuid.uuid4, primary_key=True) key = models.CharField( max_length=80, validators=[key_validator], default=default_key, help_text='A hex-encoded secret key of up to 20 bytes.', ) email = models.CharField( max_length=250, default='', help_text='Email address to send verification code.', ) modification_date = models.DateTimeField(auto_now=True) creation_date = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey( 'concrete.User', related_name="owned_%(class)ss", null=True, on_delete=models.PROTECT, ) @property def id(self): return self.pk @property def bin_key(self): return unhexlify(self.key.encode()) def generate_challenge(self): code_timeout = settings.TWO_FACTOR_CODE_TIMEOUT_SECONDS token = totp(self.bin_key, step=code_timeout) main_app = apps.get_app_config('concrete') body = settings.TWO_FACTOR_TOKEN_MSG.format( platform_name=settings.PLATFORM_NAME, confirm_code=token, min_validity=int(code_timeout / 60), ) main_app.models['email'].objects.create( receiver=self.user, body=body, resource_status='to-send', subject='[Confirmation authentication {}]'.format( settings.PLATFORM_NAME), created_by=self.user, ) logging.debug('Confirm Email has been sent to {}'.format( self.user.email)) return token def verify_token(self, token): try: token = int(token) except Exception: verified = False else: verified = any( totp( self.bin_key, step=settings.TWO_FACTOR_CODE_TIMEOUT_SECONDS, drift=drift, ) == token for drift in [0, -1]) return verified @classmethod def from_persistent_id(cls, persistent_id): """ Loads a device from its persistent id:: device == Device.from_persistent_id(device.persistent_id) """ device = None try: model_label, device_id = persistent_id.rsplit('/', 1) app_label, model_name = model_label.split('.') main_app = apps.get_app_config(app_label) device_cls = main_app.models[model_name] if issubclass(device_cls, Device): device = device_cls.objects.filter(pk=device_id).first() except (ValueError, LookupError): pass return device
class RoadMarkingPlan( UpdatePlanLocationMixin, SourceControlModel, SoftDeleteModel, UserControlModel ): id = models.UUIDField( primary_key=True, unique=True, editable=False, default=uuid.uuid4 ) location = models.GeometryField(_("Location (2D)"), srid=settings.SRID) 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.ROAD_MARKING) ), ) line_direction = EnumField( LineDirection, verbose_name=_("Line direction"), max_length=10, default=LineDirection.FORWARD, blank=True, null=True, ) arrow_direction = EnumField( ArrowDirection, verbose_name=_("Arrow direction"), max_length=10, blank=True, null=True, ) value = models.CharField( _("Road Marking value"), max_length=254, blank=True, null=True ) size = models.CharField(_("Size"), max_length=254, blank=True, null=True) material = models.CharField(_("Material"), max_length=254, blank=True, null=True) color = EnumIntegerField( RoadMarkingColor, verbose_name=_("Color"), default=RoadMarkingColor.WHITE, blank=True, null=True, ) decision_date = models.DateField(_("Decision date")) decision_id = models.CharField( _("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 ) traffic_sign_plan = models.ForeignKey( TrafficSignPlan, verbose_name=_("Traffic Sign Plan"), on_delete=models.PROTECT, blank=True, null=True, ) plan = models.ForeignKey( Plan, verbose_name=_("Plan"), on_delete=models.PROTECT, related_name="road_marking_plans", blank=True, null=True, ) type_specifier = models.CharField( _("Type specifier"), max_length=254, 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, ) symbol = models.CharField(_("Symbol"), 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_SIDE_OF_LANE, blank=True, null=True, ) length = models.IntegerField(_("Length"), blank=True, null=True) width = models.IntegerField(_("Width"), blank=True, null=True) is_raised = models.BooleanField(_("Is raised"), null=True) is_grinded = models.BooleanField(_("Is grinded"), null=True) additional_info = models.TextField(_("Additional info"), blank=True, null=True) amount = models.CharField(_("Amount"), max_length=254, blank=True, null=True) objects = SoftDeleteQuerySet.as_manager() class Meta: db_table = "road_marking_plan" verbose_name = _("Road Marking Plan") verbose_name_plural = _("Road Marking Plans") unique_together = ["source_name", "source_id"] def __str__(self): return f"{self.id} {self.device_type} {self.value}" def save(self, *args, **kwargs): if not self.device_type.validate_relation(DeviceTypeTargetModel.ROAD_MARKING): raise ValidationError( f'Device type "{self.device_type}" is not allowed for road markings' ) super().save(*args, **kwargs) if ( self.device_type.type == TrafficControlDeviceTypeType.TRANSVERSE and self.road_name == "" ): raise ValidationError( f'Road name is required for "{TrafficControlDeviceTypeType.TRANSVERSE.value}" road marking' )
class Violation(models.Model, BaseModel, SourcesMixin, VersionsMixin, GetComplexFieldNameMixin): uuid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True) published = models.BooleanField(default=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Dates and status self.startdate = ComplexFieldContainer(self, ViolationStartDate) self.first_allegation = ComplexFieldContainer( self, ViolationFirstAllegation) self.enddate = ComplexFieldContainer(self, ViolationEndDate) self.last_update = ComplexFieldContainer(self, ViolationLastUpdate) self.status = ComplexFieldContainer(self, ViolationStatus) # Location fields self.locationdescription = ComplexFieldContainer( self, ViolationLocationDescription) self.adminlevel1 = ComplexFieldContainer(self, ViolationAdminLevel1) self.adminlevel2 = ComplexFieldContainer(self, ViolationAdminLevel2) self.osmname = ComplexFieldContainer(self, ViolationOSMName) self.osmid = ComplexFieldContainer(self, ViolationOSMId) self.division_id = ComplexFieldContainer(self, ViolationDivisionId) self.location = ComplexFieldContainer(self, ViolationLocation) self.location_name = ComplexFieldContainer(self, ViolationLocationName) self.location_id = ComplexFieldContainer(self, ViolationLocationId) # Descriptions and other attributes self.description = ComplexFieldContainer(self, ViolationDescription) self.perpetrator = ComplexFieldListContainer(self, ViolationPerpetrator) self.perpetratororganization = ComplexFieldListContainer( self, ViolationPerpetratorOrganization) self.perpetratorclassification = ComplexFieldListContainer( self, ViolationPerpetratorClassification) self.types = ComplexFieldListContainer(self, ViolationType) self.complex_fields = [ self.startdate, self.first_allegation, self.enddate, self.last_update, self.status, self.locationdescription, self.adminlevel1, self.adminlevel2, self.location, self.description, self.division_id ] self.complex_lists = [ self.perpetrator, self.perpetratororganization, self.perpetratorclassification, self.types ] self.required_fields = [self.description, self.startdate, self.enddate] def get_value(self): return self.description.get_value() @property def related_entities(self): """ Return a list of dicts with metadata for all of the entities linked to this Violation. Metadata dicts must have the following keys: - name - entity_type - url (a link to edit the entity) """ related_entities = [] # Second-highest administrative level for the location of the violation. if self.adminlevel1.get_value(): location = self.adminlevel1.get_value().value related_entities.append({ 'name': location.name, 'entity_type': _('AdminLevel1'), 'url': reverse('edit-violation', args=[self.uuid]) }) # Highest administrative level for the location of the violation. if self.adminlevel2.get_value(): location = self.adminlevel2.get_value().value related_entities.append({ 'name': location.name, 'entity_type': _('AdminLevel2'), 'url': reverse('edit-violation', args=[self.uuid]) }) # The location of the violation. if self.location.get_value(): location = self.location.get_value().value related_entities.append({ 'name': location.name, 'entity_type': _('Location'), 'url': reverse('edit-violation', args=[self.uuid]) }) # The perpetrators of the violation (personnel). perpetrators = self.perpetrator.get_list() if perpetrators: perpetrators = [ perp.get_value() for perp in perpetrators if perp.get_value() ] for perpetrator in perpetrators: person = perpetrator.value related_entities.append({ 'name': person.name.get_value().value, 'entity_type': _('Perpetrator'), 'url': reverse('edit-violation', args=[self.uuid]) }) # The perpetrators of the violation (organizations). perpetratororganizations = self.perpetratororganization.get_list() if perpetratororganizations: perpetrators = [ perp.get_value() for perp in perpetratororganizations if perp.get_value() ] for perpetrator in perpetrators: org = perpetrator.value related_entities.append({ 'name': org.name.get_value().value, 'entity_type': _('PerpetratorOrganization'), 'url': reverse('edit-violation', args=[self.uuid]) }) return related_entities
class Country(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(unique=True, max_length=100) class Meta: db_table = 'countries'
class TileModel(models.Model): #Tile """ the data JSONField has this schema: values are dictionaries with n number of keys that represent nodeid's and values the value of that node instance .. code-block:: python { nodeid: node value, nodeid: node value, ... } { "20000000-0000-0000-0000-000000000002": "John", "20000000-0000-0000-0000-000000000003": "Smith", "20000000-0000-0000-0000-000000000004": "Primary" } the provisionaledits JSONField has this schema: values are dictionaries with n number of keys that represent nodeid's and values the value of that node instance .. code-block:: python { userid: { value: node value, status: "review", "approved", or "rejected" action: "create", "update", or "delete" reviewer: reviewer's user id, timestamp: time of last provisional change, reviewtimestamp: time of review } ... } { 1: { "value": { "20000000-0000-0000-0000-000000000002": "Jack", "20000000-0000-0000-0000-000000000003": "Smith", "20000000-0000-0000-0000-000000000004": "Primary" }, "status": "rejected", "action": "update", "reviewer": 8, "timestamp": "20180101T1500", "reviewtimestamp": "20180102T0800", }, 15: { "value": { "20000000-0000-0000-0000-000000000002": "John", "20000000-0000-0000-0000-000000000003": "Smith", "20000000-0000-0000-0000-000000000004": "Secondary" }, "status": "review", "action": "update", } """ tileid = models.UUIDField( primary_key=True, default=uuid.uuid1) # This field type is a guess. resourceinstance = models.ForeignKey(ResourceInstance, db_column='resourceinstanceid') parenttile = models.ForeignKey('self', db_column='parenttileid', blank=True, null=True) data = JSONField(blank=True, null=True, db_column='tiledata') # This field type is a guess. nodegroup = models.ForeignKey(NodeGroup, db_column='nodegroupid') sortorder = models.IntegerField(blank=True, null=True, default=0) provisionaledits = JSONField( blank=True, null=True, db_column='provisionaledits') # This field type is a guess. class Meta: managed = True db_table = 'tiles' def save(self, *args, **kwargs): if (self.sortorder is None or (self.provisionaledits is not None and self.data == {})): sortorder_max = TileModel.objects.filter( nodegroup_id=self.nodegroup_id, resourceinstance_id=self.resourceinstance_id).aggregate( Max('sortorder'))['sortorder__max'] self.sortorder = sortorder_max + 1 if sortorder_max is not None else 0 super(TileModel, self).save(*args, **kwargs) # Call the "real" save() method.
class Sequence(models.Model): unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) user = models.ForeignKey(UserModel, on_delete=models.CASCADE) camera_make = models.CharField(max_length=50, default='') captured_at = models.DateTimeField(null=True) created_at = models.DateTimeField(null=True) seq_key = models.CharField(max_length=100) pano = models.BooleanField(default=False) user_key = models.CharField(max_length=100) username = models.CharField(max_length=100) geometry_coordinates = models.LineStringField(null=True) geometry_coordinates_ary = ArrayField(ArrayField(models.FloatField(default=1)), null=True) coordinates_cas = ArrayField(models.FloatField(default=0), null=True) coordinates_ele = ArrayField(models.FloatField(default=0), null=True) coordinates_image = ArrayField(models.CharField(default='', max_length=100), null=True) is_uploaded = models.BooleanField(default=False) is_privated = models.BooleanField(default=False) updated_at = models.DateTimeField(default=datetime.now, blank=True) name = models.CharField(max_length=100, default='') description = models.TextField(default='') transport_type = models.ForeignKey(TransType, on_delete=models.CASCADE, null=True) tag = models.ManyToManyField(Tag) image_count = models.IntegerField(default=0) is_mapillary = models.BooleanField(default=True) is_published = models.BooleanField(default=True) def getImageCount(self): if not self.coordinates_image is None: return len(self.coordinates_image) else: return 0 def getTransportType(self): captureType = [] for t in self.type: cType = TransType.objects.get(pk=t) if cType: captureType.append(cType.name) if len(captureType) > 0: return ', '.join(captureType) else: return '' def getTagStr(self): tags = [] if self.tag.all().count() == 0: return '' for tag in self.tag.all(): if tag and tag.is_actived: tags.append(tag.name) if len(tags) > 0: return ', '.join(tags) else: return '' def getTags(self): tags = [] if self.tag.all().count() == 0: return '' for tag in self.tag.all(): if tag and tag.is_actived: tags.append(tag.name) return tags def getShortDescription(self): description = self.description if len(description) > 100: return description[0:100] + '...' else: return description def getFirstImageKey(self): if len(self.coordinates_image) > 0: return self.coordinates_image[0] else: return '' def getLikeCount(self): liked_guidebook = SequenceLike.objects.filter(sequence=self) if not liked_guidebook: return 0 else: return liked_guidebook.count() def getDistance(self): all_distance = 0 if (len(self.geometry_coordinates_ary) > 0): first_point = self.geometry_coordinates_ary[0] for i in range(len(self.geometry_coordinates_ary) - 1): if i == 0: continue second_point = self.geometry_coordinates_ary[i] d = distance(first_point, second_point) first_point = second_point all_distance += d all_distance = "%.3f" % all_distance return all_distance def getCoverImage(self): image_keys = self.coordinates_image if len(image_keys) > 0: return image_keys[0] else: return None def getFirstPointLat(self): lat = self.geometry_coordinates_ary[0][1] return lat def getFirstPointLng(self): lng = self.geometry_coordinates_ary[0][0] return lng
class Organisation(models.Model): class Meta(object): verbose_name = "Organisation" verbose_name_plural = "Organisations" ordering = ('slug', ) legal_name = models.CharField( verbose_name="Dénomination sociale", max_length=100, unique=True, db_index=True, ) organisation_type = models.ForeignKey( to='OrganisationType', verbose_name="Type d'organisation", blank=True, null=True, on_delete=models.SET_NULL, ) jurisdiction = models.ForeignKey( to='Jurisdiction', verbose_name="Territoire de compétence", blank=True, null=True, ) slug = models.SlugField( verbose_name="Slug", max_length=100, unique=True, db_index=True, ) ckan_id = models.UUIDField( verbose_name="Ckan UUID", default=uuid.uuid4, editable=False, ) website = models.URLField( verbose_name="Site internet", blank=True, null=True, ) email = models.EmailField( verbose_name="Adresse e-mail", blank=True, null=True, ) description = models.TextField( verbose_name='Description', blank=True, null=True, ) logo = models.ImageField( verbose_name="Logo", blank=True, null=True, upload_to='logos/', ) address = models.TextField( verbose_name="Adresse", blank=True, null=True, ) postcode = models.CharField( verbose_name="Code postal", max_length=100, blank=True, null=True, ) city = models.CharField( verbose_name="Ville", max_length=100, blank=True, null=True, ) phone = models.CharField( verbose_name="Téléphone", max_length=10, blank=True, null=True, ) license = models.ForeignKey( to='License', verbose_name="Licence", on_delete=models.CASCADE, blank=True, null=True, ) is_active = models.BooleanField( verbose_name="Organisation active", default=False, ) is_crige_partner = models.BooleanField( verbose_name="Organisation partenaire du CRIGE", default=False, ) geonet_id = models.TextField( verbose_name="UUID de la métadonnées", unique=True, db_index=True, blank=True, null=True, ) def __str__(self): return self.legal_name @property def logo_url(self): try: return urljoin(settings.DOMAIN_NAME, self.logo.url) except (ValueError, Exception): return None @property def full_address(self): return "{} - {} {}".format(self.address, self.postcode, self.city) @property def ows_url(self): if MRAHandler.is_workspace_exists(self.slug): return OWS_URL_PATTERN.format(organisation=self.slug) @property def ows_settings(self): if MRAHandler.is_workspace_exists(self.slug): return MRAHandler.get_ows_settings('ows', self.slug) @property def api_location(self): kwargs = {'organisation_name': self.slug} return reverse('api:organisation_show', kwargs=kwargs) @property def members(self): Dataset = apps.get_model(app_label='idgo_admin', model_name='Dataset') Profile = apps.get_model(app_label='idgo_admin', model_name='Profile') LiaisonsContributeurs = apps.get_model( app_label='idgo_admin', model_name='LiaisonsContributeurs') LiaisonsReferents = apps.get_model(app_label='idgo_admin', model_name='LiaisonsReferents') filter = reduce(ior, [ Q(organisation=self.pk), reduce(iand, [ Q(liaisonscontributeurs__organisation=self.pk), Q(liaisonscontributeurs__validated_on__isnull=False) ]), reduce(iand, [ Q(liaisonsreferents__organisation=self.pk), Q(liaisonsreferents__validated_on__isnull=False), ]) ]) profiles = Profile.objects.filter(filter).distinct().order_by( 'user__username') data = [{ 'username': member.user.username, 'full_name': member.user.get_full_name(), 'is_member': Profile.objects.filter(organisation=self.pk, id=member.id).exists(), 'is_contributor': LiaisonsContributeurs.objects.filter( profile=member, organisation__id=self.pk, validated_on__isnull=False).exists(), 'is_referent': LiaisonsReferents.objects.filter( profile=member, organisation__id=self.pk, validated_on__isnull=False).exists(), 'crige_membership': member.crige_membership, 'datasets_count': len( Dataset.objects.filter(organisation=self.pk, editor=member.user)), 'profile_id': member.id } for member in profiles] return data def get_datasets(self, **kwargs): Dataset = apps.get_model(app_label='idgo_admin', model_name='Dataset') return Dataset.objects.filter(organisation=self, **kwargs) def get_crige_membership(self): Profile = apps.get_model(app_label='idgo_admin', model_name='Profile') qs = Profile.objects.filter(organisation=self, crige_membership=True) return [profile.user for profile in qs] def get_members(self): """Retourner la liste des utilisateurs membres de l'organisation.""" Profile = apps.get_model(app_label='idgo_admin', model_name='Profile') profiles = Profile.objects.filter(organisation=self, membership=True, is_active=True) return [e.user for e in profiles] def get_contributors(self): """Retourner la liste des utilisateurs contributeurs de l'organisation.""" Nexus = apps.get_model(app_label='idgo_admin', model_name='LiaisonsContributeurs') entries = Nexus.objects.filter(organisation=self, validated_on__isnull=False) return [e.profile.user for e in entries if e.profile.is_active] def get_referents(self): """Retourner la liste des utilisateurs référents de l'organisation.""" Nexus = apps.get_model(app_label='idgo_admin', model_name='LiaisonsReferents') entries = Nexus.objects.filter(organisation=self, validated_on__isnull=False) return [e.profile.user for e in entries if e.profile.is_active]
class User(AbstractUser, BaseModel): """ User model. """ class Sex: male = 'M' female = 'F' none = 'N' choices = ( (male, _('Male')), (female, _('Female')), (none, _('None')), ) username_validator = InternationNubmerValidator() username = models.CharField( _('username'), max_length=15, unique=True, help_text=_('Required. 15 characters or fewer. ' 'Validated by ITU-T recommendation (E.164).'), validators=[username_validator], error_messages={ 'unique': _('A user with that username already exists.'), }, ) first_name = None last_name = None created_at = property(lambda x: x.date_joined) date_joined = models.DateTimeField(_('date joined'), default=timezone.now, db_column='created_at') display_name = models.CharField(max_length=32, blank=True, help_text=_('Display name.')) avatar_uuid = models.UUIDField(blank=True, null=True) device_id = models.CharField(max_length=180, blank=True, null=True) sex = models.CharField( max_length=1, choices=Sex.choices, # default=Sex.none, help_text='Valid Values: M or F') location = models.PointField(blank=True, null=True, help_text='Last user location.') birth_date = models.DateField(null=True, default=None, help_text=_('Birtht date.')) sms_code = models.OneToOneField(SMSCode, null=True, on_delete=models.CASCADE, related_name='sms_code') confirm_tos = models.BooleanField(default=False, help_text='User confirm the offer.') last_activity = models.DateTimeField(blank=True, null=True, help_text='Last user activity') show_activity = models.BooleanField( default=True, help_text='Show user ativity to others') is_restricted = models.BooleanField(default=False, help_text='User is blocked.') is_online = models.BooleanField(default=False, help_text='Is user online') black_list = models.ManyToManyField('self', blank=True, symmetrical=False) interests = TaggableManager(verbose_name='interests') class Meta: db_table = 'users' @cached_property def owner(self): return self @property def phone(self): return str(self.username) @property def is_new_user(self): """ Новый требующий дополнительные шаги для заполнения профиля. """ return not self.confirm_tos def delete(self, *args, **kwargs): """ Не удаляем пользователей. """ self.deleted_at = timezone.now() self.save() def black_listed(self, user): """ Пользователь входит в черный список. """ return self.black_list.filter(pk=user.pk).exists() def get_avatar_url(self): if self.avatar_uuid: return get_public_url(self.avatar_uuid, prefix='av')
class Node(models.Model): """ Name is unique across all resources because it ties a node to values within tiles. Recommend prepending resource class to node name. """ nodeid = models.UUIDField(primary_key=True, default=uuid.uuid1) name = models.TextField() description = models.TextField(blank=True, null=True) istopnode = models.BooleanField() ontologyclass = models.TextField(blank=True, null=True) datatype = models.TextField() nodegroup = models.ForeignKey(NodeGroup, db_column='nodegroupid', blank=True, null=True) graph = models.ForeignKey(GraphModel, db_column='graphid', blank=True, null=True) config = JSONField(blank=True, null=True, db_column='config') issearchable = models.BooleanField(default=True) isrequired = models.BooleanField(default=False) sortorder = models.IntegerField(blank=True, null=True, default=0) def get_child_nodes_and_edges(self): """ gather up the child nodes and edges of this node returns a tuple of nodes and edges """ nodes = [] edges = [] for edge in Edge.objects.filter(domainnode=self): nodes.append(edge.rangenode) edges.append(edge) child_nodes, child_edges = edge.rangenode.get_child_nodes_and_edges( ) nodes.extend(child_nodes) edges.extend(child_edges) return (nodes, edges) @property def is_collector(self): return str(self.nodeid) == str( self.nodegroup_id) and self.nodegroup is not None def get_relatable_resources(self): relatable_resource_ids = [ r2r.resourceclassfrom for r2r in Resource2ResourceConstraint.objects.filter( resourceclassto_id=self.nodeid) ] relatable_resource_ids = relatable_resource_ids + [ r2r.resourceclassto for r2r in Resource2ResourceConstraint.objects.filter( resourceclassfrom_id=self.nodeid) ] return relatable_resource_ids def set_relatable_resources(self, new_ids): old_ids = [res.nodeid for res in self.get_relatable_resources()] for old_id in old_ids: if old_id not in new_ids: Resource2ResourceConstraint.objects.filter( Q(resourceclassto_id=self.nodeid) | Q(resourceclassfrom_id=self.nodeid), Q(resourceclassto_id=old_id) | Q(resourceclassfrom_id=old_id)).delete() for new_id in new_ids: if new_id not in old_ids: new_r2r = Resource2ResourceConstraint.objects.create( resourceclassfrom_id=self.nodeid, resourceclassto_id=new_id) new_r2r.save() class Meta: managed = True db_table = 'nodes'
class Organization(AuditModel): org_guid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name="Organization UUID") name = models.CharField(max_length=200, db_comment='Company\'s Doing Business As name.') street_address = models.CharField( max_length=100, null=True, blank=True, verbose_name='Street Address', db_comment='Street address used for mailing address for the company.') city = models.CharField( max_length=50, null=True, blank=True, verbose_name='Town/City', db_comment='City used for mailing address for the company.') province_state = models.ForeignKey( ProvinceStateCode, db_column='province_state_code', on_delete=models.PROTECT, verbose_name='Province/State', related_name='companies', db_comment= 'Province or state used for the mailing address for the company') postal_code = models.CharField( max_length=10, null=True, blank=True, verbose_name='Postal Code', db_comment='Postal code used for mailing address for the company') main_tel = models.CharField( null=True, blank=True, max_length=15, verbose_name='Telephone number', db_comment='Telephone number used to contact the company') fax_tel = models.CharField( null=True, blank=True, max_length=15, verbose_name='Fax number', db_comment='Fax number used to contact the company') website_url = models.URLField( null=True, blank=True, verbose_name='Website', db_comment='The web address associated with the company') effective_date = models.DateTimeField( default=timezone.now, null=False, db_comment= 'The date the the organization record became available for use.') expiry_date = models.DateTimeField( default=timezone.make_aware(timezone.datetime.max, timezone.get_default_timezone()), null=False, db_comment= ('The date that the organization record was soft deleted (expired) by the staff.' ' Common reasons for deleting the record would be to remove duplicates, and' ' erroneous entries there by making these organizations unavailable for use.' )) email = models.EmailField( blank=True, null=True, verbose_name="Email adddress", db_comment= ('The email address for a company, this is different from the email for the individual ' 'who is a registered driller or pump installer.')) history = GenericRelation(Version) class Meta: db_table = 'registries_organization' ordering = ['name'] verbose_name_plural = 'Organizations' db_table_comment = 'Placeholder table comment.' def __str__(self): return self.name @property def org_verbose_name(self): prov = self.province_state.province_state_code # display either "City, Province" or just "Province" location = '{}, {}'.format(self.city, prov) if ( self.city is not None and len(self.city) is not 0) else prov return '{} ({})'.format(self.name, location) @property def mailing_address(self): address = [ self.street_address, self.city, self.province_state_id, self.postal_code, ] return ", ".join([part for part in address if part])