class WithOtherDetails(Model): other_details = TextField('alte detalii', max_length=500, blank=True, default=None) vices = TextField('vicii', max_length=500, blank=True, default=None) display_expiry_date = DateTimeField('dată expirare afişare', blank=True, default=None) disponibility = TextField('disponibilitate proprietate', blank=True, default=None) class Meta: abstract = True
class WithPropertyInfo(Model): property_name = CharField('nume proprietate', max_length=100, default=None, blank=True) property_description = TextField('descriere proprietate', max_length=100, default=None, blank=True) total_surface = DecimalField('suprafaţă totală (mp)', max_digits=6, decimal_places=2, default=None) terrain_surface = DecimalField('suprafaţă teren (mp)', max_digits=6, decimal_places=2, default=None, blank=True) class Meta: abstract = True
class WithPrice(Model): not_include_vat = BooleanField('nu include TVA', default=False) price_details = TextField('alte detalii preţ', blank=True, default=None) zero_commission = BooleanField('comision 0%', default=False) buyer_commission = CharField('comision cumpărător', max_length=50, blank=True, default=None) class Meta: abstract = True
class JobCluster(Model): name = CharField(max_length=128) owner = ForeignKey(User, null=True, blank=True, on_delete=SET_NULL) host = CharField(max_length=1024) port = PositiveIntegerField() token = CharField(max_length=1024) cert = TextField(max_length=2048) def __str__(self): return self.name
class LayerMeta(Model): """ Immutable state of layer uploading & geoprocessing progress. To maintain an audit trail of each status change for a layer, these records should *not* be mutated. Instead, a new record should be created for each status change. The data in this table will primarily be maintained by the geoprocessing side of things. """ layer = ForeignKey(Layer, related_name='layer_metas') state = CharField(max_length=16) error = TextField(null=True, blank=True) thumb_small = URLField( null=True, blank=True, help_text='80x80 pixels', ) thumb_large = URLField( null=True, blank=True, help_text='400x150 pixels', ) created_at = DateTimeField(auto_now_add=True) # TileJSON fields min_zoom = IntegerField(default=0) max_zoom = IntegerField(default=11) bounds = CharField( null=True, max_length=120, help_text='JSON array', ) center = CharField( null=True, max_length=60, help_text='JSON array', ) def to_json(self): return { 'id': self.id, 'state': self.state, 'error': self.error, 'thumb_small': self.thumb_small, 'thumb_large': self.thumb_large, 'created_at': self.created_at.isoformat(), 'min_zoom': self.min_zoom, 'max_zoom': self.max_zoom, 'bounds': self.bounds, 'center': self.center, }
class BaseOfferModel(BaseModel): agent = ForeignKey(UserProfile, on_delete=SET_NULL, null=True) is_published = BooleanField('publicat?', default=False) address = PointField('adresă', max_length=200, null=True) hide_address_on_imobiliare = BooleanField('ascunde adresa în imobiliare.ro', default=False) county = ForeignKey(County, related_name='%(class)ss', related_query_name='%(class)s', on_delete=SET_NULL, null=True, verbose_name='judeţ') locality = ForeignKey(Locality, related_name='%(class)ss', related_query_name='%(class)s', on_delete=SET_NULL, null=True, verbose_name='localitate') sector = CharField('sectorul', max_length=2, blank=True, default=None, choices=Sector.choices) hide_exact_location_on_imobiliare = BooleanField('ascunde localizarea exactă pe imobiliare.ro', default=False) postal_code = CharField('cod poştal', max_length=50, blank=True, default=None) neighborhood = CharField('vecinătăţi', max_length=100, blank=True, default=None) description = TextField('descriere emoţională', default=None, blank=True) class Meta: abstract = True
class LandBaseModel(BaseOfferModel, WithOtherDetails, WithDestination, WithExclusivity, WithOtherZoneDetails): land_type = CharField('tip teren', max_length=12, choices=LandType.choices, default=LandType.CONSTRUCTII) street_fronts_nr = PositiveIntegerField('nr. fronturi stradale', default=None, blank=True) classification = CharField('clasificare', max_length=12, choices=LandClassification.choices, default=LandClassification.INTRAVILAN) street_front = PositiveIntegerField('front stradal (m)', default=None, blank=True) terrain_surface = PositiveIntegerField('suprafaţă teren', default=None, blank=True) terrain_surface_type = CharField('', max_length=3, choices=SurfaceType.choices, default=SurfaceType.M) terrain_angle = IntegerField('înclinaţie teren (%)', default=None, blank=True) pot = IntegerField('P.O.T (%)', default=None, blank=True, help_text='Procentajul de ocupare al terenului (suprafaţa ocupată/suprafaţa totală * 100') cut = IntegerField('C.U.T (%)', default=None, blank=True, help_text='Coeficientul de utilizare al terenului (suma suprafeţelor desfăşurate/suprafaţa totală * 100') height_regime = PositiveIntegerField('regim înălţime (m)', default=None, blank=True) urban_coefficients_source = CharField('sursă informaţii coef. urbanistici', max_length=25, choices=URBAN_COEFF_SOURCES, default=None, blank=True) terrain_construction = BooleanField('construcţie pe teren', default=False) constructed_surface = PositiveIntegerField('suprafaţă construită (mp)', default=None, blank=True) access_road_width = PositiveIntegerField('lăţime drum de acces (m)', default=None, blank=True) documents = TextField('acte/avize', default=None, blank=True) plots_lot = BooleanField('lot parcele', default=False) plots_lot_name = CharField('nume lot parcele', max_length=100, default=None, blank=True) has_current = BooleanField('curent', default=False) has_three_phase_current = BooleanField('curent trifazic', default=False) has_water = BooleanField('apă', default=False) has_sewerage = BooleanField('canalizare', default=False) has_irrigation_system = BooleanField('sistem irigaţie', default=False) has_gas = BooleanField('gaz', default=False) has_utilities_nearby = BooleanField('utilităţi în zonă', default=False) has_investment_opportunity = BooleanField('oportunitate de investiţie', default=False) can_be_demolished = BooleanField('construcţie demolabilă', default=False) can_be_splitted = BooleanField('parcelabil', default=False) is_near_road = BooleanField('la şosea', default=False) has_auto_access = BooleanField('acces auto', default=False) is_surrounded_terrain = BooleanField('teren împrejmuit', default=False) class Meta: abstract = True
class Place(Base): # attribution attribution = TextField(null=True, blank=True, db_index=DB_INDEX) # concordances enwiki_title = TextField(null=True, blank=True, db_index=DB_INDEX) geonames_id = IntegerField(null=True, blank=True, db_index=DB_INDEX) osm_id = TextField(null=True, blank=True, db_index=DB_INDEX) pcode = TextField(null=True, blank=True, db_index=DB_INDEX) fips = IntegerField(null=True, blank=True, db_index=DB_INDEX) # admin stuff admin1_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin2_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin3_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin4_code = TextField(null=True, blank=True, db_index=DB_INDEX) admin_level = IntegerField(null=True, blank=True, db_index=DB_INDEX) # bounding box stuff east = FloatField(null=True, blank=True) north = FloatField(null=True, blank=True) south = FloatField(null=True, blank=True) west = FloatField(null=True, blank=True) # name stuff name = TextField(null=True, blank=True, db_index=DB_INDEX) name_ascii = TextField(null=True, blank=True, db_index=DB_INDEX) name_display = TextField(null=True, blank=True, db_index=DB_INDEX) name_en = TextField(null=True, blank=True, db_index=DB_INDEX) name_normalized = TextField(null=True, blank=True, db_index=DB_INDEX) other_names = TextField(null=True, blank=True, db_index=False) # place types geonames_feature_class = TextField(null=True, blank=True, db_index=DB_INDEX) geonames_feature_code = TextField(null=True, blank=True, db_index=DB_INDEX) place_type = TextField(null=True, blank=True, db_index=DB_INDEX) # geometries objects = GeoManager() latitude = FloatField(null=True, blank=True) longitude = FloatField(null=True, blank=True) mls = MultiLineStringField(null=True, blank=True) mpoly = MultiPolygonField(null=True, blank=True) point = PointField(null=True, blank=True) area_sqkm = IntegerField(null=True, blank=True) # osm stuff importance = FloatField(null=True, blank=True) osmname_class = TextField(null=True, blank=True, db_index=DB_INDEX) osmname_type = TextField(null=True, blank=True, db_index=DB_INDEX) osm_type = TextField(null=True, blank=True, db_index=DB_INDEX) place_rank = IntegerField(null=True, blank=True, db_index=DB_INDEX) # dem and elevation stuff dem = FloatField(null=True, blank=True) elevation = FloatField(null=True, blank=True) # geocoder stuff city = TextField(null=True, blank=True, db_index=DB_INDEX) county = TextField(null=True, blank=True, db_index=DB_INDEX) # should be 100 country = TextField(null=True, blank=True, db_index=DB_INDEX) country_code = TextField(null=True, blank=True, db_index=DB_INDEX) state = TextField(null=True, blank=True, db_index=DB_INDEX) street = TextField(null=True, blank=True, db_index=DB_INDEX) #misc note = TextField(null=True, blank=True) population = BigIntegerField(null=True, blank=True, db_index=DB_INDEX) # number of times name appeared and meant this place minus number of times didn't mean this place popularity = BigIntegerField(null=True, blank=True, db_index=DB_INDEX) timezone = TextField(null=True, blank=True, db_index=DB_INDEX) topic = ForeignKey("Topic", null=True, on_delete=SET_NULL, db_index=DB_INDEX) # represents the most common topic associated with this place wikidata_id = TextField(null=True, blank=True, db_index=DB_INDEX) def get_all_names(self): if not hasattr(self, "all_names"): names = set() names.add(self.name) names.add(self.name_ascii) names.add(self.name_display) names.add(self.name_en) names.add(self.name_normalized) if self.other_names: for other_name in self.other_names.split(","): names.add(other_name) names.discard(None) self.all_names = names return self.all_names class Meta: ordering = ['name']
class LayerImage(Model): """ Geospatial image uploaded by the user. `meta_json` may be populated during geoprocessing and should contain a serialized JSON blob of image metadata. This blob should be in the form of key value pairs (object literal) and will be displayed as-is. """ layer = ForeignKey(Layer, related_name='layer_images') priority = IntegerField( default=0, help_text='The order which images are layered (starting from 0)') thumb_small_key = CharField(max_length=255, blank=True, default='', help_text='S3 key for small thumbnail') thumb_large_key = CharField(max_length=255, blank=True, default='', help_text='S3 key for large thumbnail') meta_json = TextField( null=True, blank=True, help_text='Serialized JSON of image metadata', ) min_x = FloatField(default=None, blank=True, null=True) max_x = FloatField(default=None, blank=True, null=True) min_y = FloatField(default=None, blank=True, null=True) max_y = FloatField(default=None, blank=True, null=True) file_name = CharField( blank=False, default='', max_length=255, help_text='Filename of original file', ) s3_uuid = UUIDField(default=uuid.uuid4, editable=False) file_extension = CharField(blank=False, default='', max_length=10, help_text='Extension of file') bucket_name = CharField(blank=False, default='', max_length=255, help_text='Name of S3 bucket') source_s3_bucket_key = CharField( blank=True, null=True, max_length=255, help_text='S3 <bucket>/<key> for source image (optional)') status_created = DateTimeField(auto_now_add=True) status_transfer_start = DateTimeField(null=True, blank=True) status_transfer_end = DateTimeField(null=True, blank=True) status_validate_start = DateTimeField(null=True, blank=True) status_validate_end = DateTimeField(null=True, blank=True) status_thumbnail_start = DateTimeField(null=True, blank=True) status_thumbnail_end = DateTimeField(null=True, blank=True) status_upload_error = CharField(max_length=255, blank=True, null=True) status_transfer_error = CharField(max_length=255, blank=True, null=True) status_validate_error = CharField(max_length=255, blank=True, null=True) status_thumbnail_error = CharField(max_length=255, blank=True, null=True) def reset(self): """ Resets fields to prepare for a retry. """ self.status_transfer_start = None self.status_transfer_end = None self.status_validate_start = None self.status_validate_end = None self.status_thumbnail_start = None self.status_thumbnail_end = None self.status_upload_error = None self.status_transfer_error = None self.status_validate_error = None self.status_thumbnail_error = None self.save() def has_been_validated(self): return self.status_validate_end is not None def is_copy_image(self): return self.source_s3_bucket_key is not None def get_s3_key(self): return '%d-%s.%s' % (self.layer.user.id, self.s3_uuid, self.file_extension) def get_s3_uri(self): return 's3://{}/{}-{}.{}'.format(self.bucket_name, self.layer.user_id, self.s3_uuid, self.file_extension) def to_json(self): return { 'id': self.id, 'thumb_small': generate_thumb_url(self.thumb_small_key), 'thumb_large': generate_thumb_url(self.thumb_large_key), 'meta_json': self.meta_json, 'min_x': self.min_x, 'max_x': self.max_x, 'min_y': self.min_y, 'max_y': self.max_y, 'file_name': self.file_name, 's3_uuid': str(self.s3_uuid), 'file_extension': self.file_extension, 'bucket_name': self.bucket_name, 'source_s3_bucket_key': self.source_s3_bucket_key, 'status_created': self.status_created is not None, 'status_transfer_start': self.status_transfer_start is not None, 'status_transfer_end': self.status_transfer_end is not None, 'status_validate_start': self.status_validate_start is not None, 'status_validate_end': self.status_validate_end is not None, 'status_thumbnail_start': self.status_thumbnail_start is not None, 'status_thumbnail_end': self.status_thumbnail_end is not None, 'status_upload_error': self.status_upload_error, 'status_transfer_error': self.status_transfer_error, 'status_validate_error': self.status_validate_error, 'status_thumbnail_error': self.status_thumbnail_error, 'source_s3_bucket_key': self.source_s3_bucket_key } def update_status_start(self, status): value = datetime.now() if status == enums.STATUS_TRANSFER: self.status_transfer_start = (value if self.status_transfer_start is None else self.status_transfer_start) elif status == enums.STATUS_VALIDATE: self.status_validate_start = (value if self.status_validate_start is None else self.status_validate_start) elif status == enums.STATUS_THUMBNAIL: self.status_thumbnail_start = (value if self.status_thumbnail_start is None else self.status_thumbnail_start) def update_status_end(self, status, error_message=None): value = datetime.now() if status == enums.STATUS_TRANSFER: self.status_transfer_end = value self.status_transfer_error = error_message elif status == enums.STATUS_VALIDATE: self.status_validate_end = value self.status_validate_error = error_message elif status == enums.STATUS_THUMBNAIL: self.status_thumbnail_end = value self.status_thumbnail_error = error_message def set_bounds(self, bounds): self.min_x = bounds[0] self.max_x = bounds[1] self.min_y = bounds[2] self.max_y = bounds[3] self.save(update_fields=['min_x', 'max_x', 'min_y', 'max_y'])
class Layer(Model): """ Represents a single Image Layer which may contain one or more geospatial images. """ class Meta: unique_together = ('user', 'name') user = ForeignKey(User) name = CharField(max_length=255) slug = SlugField(max_length=255, blank=True) status_created = DateTimeField(auto_now_add=True) status_validate_start = DateTimeField(null=True, blank=True) status_validate_end = DateTimeField(null=True, blank=True) status_thumbnail_start = DateTimeField(null=True, blank=True) status_thumbnail_end = DateTimeField(null=True, blank=True) status_create_cluster_start = DateTimeField(null=True, blank=True) status_create_cluster_end = DateTimeField(null=True, blank=True) status_chunk_start = DateTimeField(null=True, blank=True) status_chunk_end = DateTimeField(null=True, blank=True) status_mosaic_start = DateTimeField(null=True, blank=True) status_mosaic_end = DateTimeField(null=True, blank=True) status_failed = DateTimeField(null=True, blank=True) status_completed = DateTimeField(null=True, blank=True) status_heartbeat = DateTimeField(null=True, blank=True) status_validate_error = CharField(max_length=255, blank=True, null=True) status_thumbnail_error = CharField(max_length=255, blank=True, null=True) status_create_cluster_error = CharField(max_length=255, blank=True, null=True) status_chunk_error = CharField(max_length=255, blank=True, null=True) status_mosaic_error = CharField(max_length=255, blank=True, null=True) status_failed_error = CharField(max_length=255, blank=True, null=True) description = TextField(blank=True) organization = CharField(max_length=255, blank=True, null=True) is_public = BooleanField(default=False) capture_start = DateField(blank=True, null=True) capture_end = DateField(blank=True, null=True) min_x = FloatField(default=None, blank=True, null=True) max_x = FloatField(default=None, blank=True, null=True) min_y = FloatField(default=None, blank=True, null=True) max_y = FloatField(default=None, blank=True, null=True) area = FloatField(default=0, blank=True, null=True) area_unit = CharField( blank=True, max_length=18, choices=enums.AREA_UNIT_CHOICES, default=enums.SQ_KM, ) projection = CharField( blank=True, max_length=18, choices=enums.PROJECTION_CHOICES, default=enums.WGS84, help_text='Source Projection', ) srid = CharField( blank=True, max_length=18, choices=enums.SRID_CHOICES, default=enums.WGS84, help_text='Source SRS', ) tile_srid = CharField(blank=True, max_length=18, choices=enums.SRID_CHOICES, default=enums.WGS84, help_text='Tile SRS') tile_format = CharField( blank=True, max_length=18, choices=enums.TILE_FORMAT_CHOICES, default=enums.OVER_PNG32, ) tile_origin = CharField( blank=True, max_length=18, choices=enums.TILE_ORIGIN_CHOICES, default=enums.TOPLEFT, help_text='Tiling Scheme', ) resampling = CharField( blank=True, max_length=18, choices=enums.TILE_RESAMPLING_CHOICES, default=enums.BILINEAR, ) transparency = CharField( blank=True, max_length=18, help_text='Hexadecimal (Ex. #00FF00)', ) dismissed = BooleanField(blank=True, default=False) created_at = DateTimeField(auto_now_add=True) updated_at = DateTimeField(auto_now=True) deleted_at = DateTimeField(null=True, blank=True) status_updated_at = DateTimeField(default=datetime.now) thumb_small_key = CharField(max_length=255, blank=True, default='', help_text='S3 key for small thumbnail') thumb_large_key = CharField(max_length=255, blank=True, default='', help_text='S3 key for large thumbnail') def has_copy_images(self): for image in self.layer_images.all(): if image.is_copy_image(): return True return False def save(self, *args, **kwargs): self.slug = slugify(self.name) super(Layer, self).save(*args, **kwargs) def to_json(self): """ Return JSON serializable model data. Note: Prefetch all related foreign key relationships before calling this method for optimal performance. """ tags = [m.to_json() for m in self.layer_tags.all()] images = [m.to_json() for m in self.layer_images.all()] capture_start = self.capture_start.isoformat() \ if self.capture_start else None capture_end = self.capture_end.isoformat() \ if self.capture_end else None return { 'id': self.id, 'name': self.name, 'slug': self.slug, 'description': self.description, 'organization': self.organization, 'is_public': self.is_public, 'capture_start': capture_start, 'capture_end': capture_end, 'area': self.area, 'area_unit': self.area_unit, 'bounds': self.get_bounds(), 'projection': self.projection, 'srid': self.srid, 'tile_srid': self.tile_srid, 'tile_format': self.tile_format, 'tile_origin': self.tile_origin, 'resampling': self.resampling, 'transparency': self.transparency, 'status_created': self.status_created is not None, 'status_validate_start': self.status_validate_start is not None, 'status_validate_end': self.status_validate_end is not None, 'status_thumbnail_start': self.status_thumbnail_start is not None, 'status_thumbnail_end': self.status_thumbnail_end is not None, 'status_create_cluster_start': (self.status_create_cluster_start is not None), 'status_create_cluster_end': (self.status_create_cluster_end is not None), 'status_chunk_start': self.status_chunk_start is not None, 'status_chunk_end': self.status_chunk_end is not None, 'status_mosaic_start': self.status_mosaic_start is not None, 'status_mosaic_end': self.status_mosaic_end is not None, 'status_failed': self.status_failed is not None, 'status_completed': self.status_completed is not None, 'status_heartbeat': self.status_completed is not None, 'status_validate_error': self.status_validate_error, 'status_thumbnail_error': self.status_thumbnail_error, 'status_create_cluster_error': self.status_create_cluster_error, 'status_chunk_error': self.status_chunk_error, 'status_mosaic_error': self.status_mosaic_error, 'status_failed_error': self.status_failed_error, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat(), 'status_updated_at': self.created_at.isoformat(), 'thumb_small': generate_thumb_url(self.thumb_small_key), 'thumb_large': generate_thumb_url(self.thumb_large_key), # Foreign key fields 'tags': tags, 'images': images, 'username': self.user.username, # Generated fields 'url': self.get_absolute_url(), 'meta_url': self.get_meta_url(), 'favorite_url': self.get_favorite_url(), 'dismiss_url': self.get_dismiss_url(), 'retry_url': self.get_retry_url(), 'tile_url': self.get_tile_url(), } def get_bounds(self): if all([self.min_x, self.min_y, self.max_x, self.max_y]): return [ [self.min_y, self.min_x], [self.max_y, self.max_x], ] return None def retry_possible(self): """ Returns true if it is possible to retry processing a layer. """ return self.status_failed is not None def reset(self): """ Resets fields to prepare for a retry. """ self.status_validate_start = None self.status_validate_end = None self.status_thumbnail_start = None self.status_thumbnail_end = None self.status_create_cluster_start = None self.status_create_cluster_end = None self.status_chunk_start = None self.status_chunk_end = None self.status_mosaic_start = None self.status_mosaic_end = None self.status_failed = None self.status_completed = None self.status_heartbeat = None self.status_validate_error = None self.status_thumbnail_error = None self.status_create_cluster_error = None self.status_chunk_error = None self.status_mosaic_error = None self.status_failed_error = None self.save() for image in self.layer_images.all(): image.reset() def get_absolute_url(self): kwargs = { 'layer_id': self.id, 'username': self.user.username, } return reverse('layer_detail', kwargs=kwargs) def get_meta_url(self): kwargs = { 'layer_id': self.id, 'username': self.user.username, } return reverse('layer_meta', kwargs=kwargs) def get_favorite_url(self): kwargs = { 'layer_id': self.id, } return reverse('create_or_destroy_favorite', kwargs=kwargs) def get_retry_url(self): return reverse('layer_retry') def get_dismiss_url(self): return reverse('layer_dismiss') def get_tile_url(self): url = 'https://s3.amazonaws.com/%s/%d/{z}/{x}/{y}.png' return url % (settings.AWS_TILES_BUCKET, self.id) def get_tile_bucket_path(self): return 's3://{}/{}'.format(settings.AWS_TILES_BUCKET, self.id) def process_failed_heartbeat(self): self.status_heartbeat = datetime.now() self.save() def update_status_start(self, status): value = datetime.now() if status == enums.STATUS_VALIDATE: self.status_validate_start = (value if self.status_validate_start is None else self.status_validate_start) elif status == enums.STATUS_THUMBNAIL: self.status_thumbnail_start = (value if self.status_thumbnail_start is None else self.status_thumbnail_start) elif status == enums.STATUS_CREATE_CLUSTER: self.status_create_cluster_start = \ (value if self.status_create_cluster_start is None else self.status_create_cluster_start) elif status == enums.STATUS_CHUNK: self.status_chunk_start = (value if self.status_chunk_start is None else self.status_chunk_start) elif status == enums.STATUS_MOSAIC: self.status_mosaic_start = (value if self.status_mosaic_start is None else self.status_mosaic_start) def update_status_end(self, status, error_message=None): value = datetime.now() if status == enums.STATUS_VALIDATE: self.status_validate_end = value self.status_validate_error = error_message elif status == enums.STATUS_THUMBNAIL: self.status_thumbnail_end = value self.status_thumbnail_error = error_message elif status == enums.STATUS_CREATE_CLUSTER: self.status_create_cluster_end = value self.status_create_cluster_error = error_message elif status == enums.STATUS_CHUNK: self.status_chunk_end = value self.status_chunk_error = error_message elif status == enums.STATUS_MOSAIC: self.status_mosaic_end = value self.status_mosaic_error = error_message elif status == enums.STATUS_COMPLETED: if error_message is not None: raise StatusMismatchError( 'Completed status does not accept errors.') self.status_completed = value # To be safe, unset the failed status and error. self.status_failed = None self.status_failed_error = None elif status == enums.STATUS_FAILED: if self.status_completed is not None: raise StatusMismatchError( 'Cannot mark completed layer as failed.') self.status_failed_error = error_message # If we had any error message mark the generic failed field. if error_message is not None: if self.status_completed is not None: raise StatusMismatchError( 'Cannot set errors on completed layer.') self.status_failed = value def mark_failed(self): value = datetime.now() self.status_failed = value def set_bounds(self, bounds): self.min_x = bounds[0] self.max_x = bounds[1] self.min_y = bounds[2] self.max_y = bounds[3] self.save(update_fields=['min_x', 'max_x', 'min_y', 'max_y']) def __unicode__(self): return '{0} -> {1}'.format(self.user.username, self.name)