class Photo(models.Model): PORTRAIT = 'P' LANDSCAPE = 'L' ORIENTATION_CHOICES = ((PORTRAIT, 'Portrait'), (LANDSCAPE, 'Landscape')) project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="photos") photo = models.ImageField(upload_to='project_photos/') orientation = models.CharField(max_length=1, choices=ORIENTATION_CHOICES) display_order = models.IntegerField() portrait = ImageSpecField(source='photo', processors=[ SmartResize(500, 700), ResizeCanvas(1110, 700, color='black') ], format='JPEG', options={'quality': 60}) landscape = ImageSpecField(source='photo', processors=[SmartResize(1110, 700)], format='JPEG', options={'quality': 60}) thumbnail = ImageSpecField(source='photo', processors=[SmartResize(400, 200)], format='JPEG', options={'quality': 60}) credit = models.CharField(max_length=100) @property def is_landscape(self): return self.orientation == self.LANDSCAPE def __str__(self): return '{} {}'.format(self.project.title, self.pk)
class Gift(TimeStampedModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(verbose_name=_('Name'), max_length=300) description = models.TextField(verbose_name=_('Description'), null=True, blank=True) link = models.TextField(verbose_name=_('Link'), null=True, blank=True) price = models.DecimalField(verbose_name=_('Price'), max_digits=7, decimal_places=2) gift_is_part = models.BooleanField(verbose_name=_('Gift is part'), default=False) max_parts = models.PositiveIntegerField(verbose_name=_('Maximum number of parts')) taken_parts = models.PositiveIntegerField(verbose_name=_('Number of parts taken'), default=0) img = models.ImageField(blank=True, null=True) img_catalog = ImageSpecField(source='img', processors=[ResizeToFit(800, 600), ResizeCanvas(800, 600)], format='JPEG', options={'quality': 60}) img_miniature = ImageSpecField(source='img', processors=[ResizeToFill(60, 60)], format='JPEG', options={'quality': 60}) def is_available(self): if self.taken_parts < self.max_parts: return True else: return False def avail_parts(self): return self.max_parts - self.taken_parts def __str__(self): return self.name def get_absolute_url(self): return reverse("wedding:gift-detail", kwargs={'pk': self.pk}) class Meta: verbose_name = "Gift" verbose_name_plural = "Gifts" permissions = ( ("edit", "Can edit the Gift list"), )
class Image(models.Model): """Image model.""" # ******************************************************************* # ******************** Language independent data ******************** # ******************************************************************* api_url = models.TextField( null=False, blank=False, unique=True, verbose_name=_("Image API URL"), ) primary = models.BooleanField(default=False, verbose_name=_("Primary image")) image = models.FileField( null=True, blank=True, upload_to='collection_images', verbose_name=_("Image"), ) image_large = ImageSpecField(source='image', processors=[ ResizeToFit(960, 960, upscale=False), ], format='JPEG', options={ 'quality': 85, 'suffix': '_large' }) image_sized = ImageSpecField(source='image_large', processors=[ ResizeToFit(108, 108, upscale=False), ResizeCanvas(128, 128), ], format='JPEG', options={ 'quality': 90, 'suffix': '_sized' }) image_ml = ImageSpecField(source='image_large', processors=[ ResizeToFit(108, 108, upscale=False), ResizeCanvas(128, 128), ], format='PNG', options={ 'quality': 90, 'suffix': '_ml' }) active = models.BooleanField( default=False, verbose_name=_("Active"), ) trimmed = models.BooleanField( default=False, verbose_name=_("Trimmed"), ) created = models.DateField( auto_now_add=True, verbose_name=_("Date imported"), ) updated = models.DateField( auto_now=True, verbose_name=_("Date updated"), ) # ******************************************************************* # ******************** Data translated into English ***************** # ******************************************************************* title = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Title"), help_text=_("Title of the object")) description = models.TextField(null=True, blank=True, verbose_name=_("Description"), help_text=_("Description of the object")) # ******************************************************************* # ******************** Original data as imported ******************** # ******************************************************************* title_orig = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Original title"), help_text=_("Title of the object")) description_orig = models.TextField( null=True, blank=True, verbose_name=_("Original description"), help_text=_("Description of the object")) class Meta(object): """Meta options.""" def __str__(self): return self.api_url
class Especialista(TimeStampedModel): CHOICES_TIPO_DOCUMENTO = ( ('CC', 'Cédula Ciudadanía'), ('CE', 'Cédula Extrangería'), ('PS', 'Pasaporte'), ('TI', 'Tarjeta Identidad'), ) CHOICES_SEXO = (('F', 'Femenino'), ('M', 'Masculino')) user = models.OneToOneField(User, related_name='especialista', on_delete=models.CASCADE) tipo_documento = models.CharField(max_length=2, choices=CHOICES_TIPO_DOCUMENTO, default='CC') nro_identificacion = models.CharField(max_length=30, unique=True) nombre = models.CharField(max_length=60) nombre_segundo = models.CharField(max_length=60, null=True, blank=True) apellido = models.CharField(max_length=60) apellido_segundo = models.CharField(max_length=60, null=True, blank=True) fecha_nacimiento = models.DateTimeField() genero = models.CharField(choices=CHOICES_SEXO, default='F', max_length=20) grupo_sanguineo = models.CharField(max_length=60, null=True, blank=True) especialidad = models.ForeignKey(Especialidad, on_delete=models.PROTECT, verbose_name='Especialidad', null=True, blank=True) universidad = models.CharField(max_length=100, blank=True, null=True) registro_profesional = models.CharField(max_length=100, null=True, blank=True) firma = ProcessedImageField( processors=[ResizeToFit(400, 300), ResizeCanvas(400, 300)], format='PNG', options={'quality': 100}, null=True, blank=True) class Meta: verbose_name_plural = 'Especialistas' verbose_name = 'Especialista' @staticmethod def existe_documento(tipo_documento: str, nro_identificacion: str) -> bool: return Especialista.objects.filter( tipo_documento=tipo_documento, nro_identificacion=nro_identificacion).exists() @property def full_name(self): nombre_segundo = '' if self.nombre_segundo: nombre_segundo = ' %s' % (self.nombre_segundo) apellido_segundo = '' if self.apellido_segundo: apellido_segundo = ' %s' % (self.apellido_segundo) return '%s%s %s%s' % (self.nombre, nombre_segundo, self.apellido, apellido_segundo) def __str__(self): return self.full_name
class Thumbnail1400(ImageSpec): processors = [ResizeToFit(1400, 1400),ResizeCanvas(1400,1400)] format = 'JPEG' options = {'quality': 100}
class Thumbnail450(ImageSpec): processors = [ResizeToFit(400, 300),ResizeCanvas(400,300)] format = 'PNG' options = {'quality': 90}
class Thumbnail80(ImageSpec): processors = [ResizeToFit(80, 65),ResizeCanvas(80,65)] format = 'PNG' options = {'quality': 90}
class Thumbnail50(ImageSpec): processors = [ResizeToFit(50, 40),ResizeCanvas(50,40)] format = 'PNG' options = {'quality': 80}
class Shape(ResultBase): """ Abstract parent describing a complex polygon. Shapes are represented as a bag of vertices, triangles, and line segments. Users submit instances of SubmittedShapes, which are then intersected and triangulated to form subclasses of Shape. """ #: min size of a shape MIN_PIXEL_AREA = 4096 #: min size of a shape for rectification MIN_PLANAR_AREA = 16384 #: Vertices format: x1,y1,x2,y2,x3,y3,... (coords are fractions of width/height) #: (this format allows easy embedding into javascript) vertices = models.TextField() #: num_vertices should be equal to len(points.split(','))//2 num_vertices = models.IntegerField(db_index=True) #: Triangles format: p1,p2,p3,p2,p3,p4..., where p_i is an index into #: vertices, and p1-p2-p3 is a triangle. Each triangle is three indices #: into points; all triangles are listed together. This format allows easy #: embedding into javascript. triangles = models.TextField() #: num_triangles should be equal to len(triangles.split(','))//3 num_triangles = models.IntegerField() #: Segments format: "p1,p2,p2,p3,...", where p_i is an index into vertices, #: and p1-p2, p2-p3, ... are the line segments. The segments are unordered. #: Each line segment is two indices into points; all segments are listed #: together. This format allows easy embedding into javascript. segments = models.TextField() #: num_segments should be equal to len(segments.split(','))//2 num_segments = models.IntegerField() ## Line segments re-grouped as poly-lines "[[p1,p2,p3,p4],[p1,p2,p3],...]", ## json encoded. Each p_i is an index into vertices. This is the exact same ## data as the segments field, but with line segments properly grouped. #polylines = models.TextField() ## Number of unique polylines; should equal len(json.loads(polylines)) #num_polylines = models.IntegerField() #: Area in normalized units. To get the pixel area, multiply this by the #: total photo area. area = models.FloatField() #: Area in pixels pixel_area = models.IntegerField(null=True, db_index=True) #: flag to separate out this shape synthetic = models.BooleanField(default=False) synthetic_slug = models.CharField(max_length=32, blank=True) #: if true, enough users voted this to be the correct type of segmentation correct = models.NullBooleanField() #: further from 0: more confident in assignment of correct correct_score = models.FloatField( blank=True, null=True, db_index=True) #: if true, enough users voted this to be flat planar = models.NullBooleanField() #: CUBAM score for the planar field. further from 0: more confident in #: assignment of planar. planar_score = models.FloatField(blank=True, null=True, db_index=True) #: method by which the planar field was set PLANAR_METHODS = (('A', 'admin'), ('C', 'CUBAM'), ('M', 'majority vote')) planar_method_to_str = dict((k, v) for (k, v) in PLANAR_METHODS) planar_method = models.CharField( max_length=1, choices=PLANAR_METHODS, blank=True, null=True) #: Photo masked by the shape and cropped to the bounding box. The masked #: (excluded) region has pixels that are white with no opacity (ARGB value #: (0, 255, 255, 255)). image_crop = models.ImageField( upload_to='shapes', blank=True, max_length=255, storage=STORAGE) #: square thumbnail with whitebackground image_square_300 = ImageSpecField( [ResizeToFit(300, 300), ResizeCanvas(300, 300, color=(255, 255, 255))], source='image_crop', format='JPEG', options={'quality': 90}, cachefile_storage=STORAGE) #: bbox: photo cropped out to the bounding box of this shape image_bbox = models.ImageField( upload_to='bbox', blank=True, max_length=255, storage=STORAGE) #: bbox resized to fit in 512x512 image_bbox_512 = ImageSpecField( [ResizeToFit(512, 512)], source='image_bbox', format='JPEG', options={'quality': 90}, cachefile_storage=STORAGE) #: bbox resized to fit in 1024x1024 (used by opengl widget in rectify task) image_bbox_1024 = ImageSpecField( [ResizeToFit(1024, 1024)], source='image_bbox', format='JPEG', options={'quality': 90}, cachefile_storage=STORAGE) #: position to show a label (normalized coordinates) label_pos_x = models.FloatField(blank=True, null=True) label_pos_y = models.FloatField(blank=True, null=True) ## json-encoded array [min x, min y, max x, max y] indicating the position ## of the bounding box. as usual, positions are specified as fractions of ## width and height. #bbox = models.TextField(blank=True) ## bbox width/height aspect ratio #bbox_aspect_ratio = models.FloatField(null=True, blank=True) #: padded bounding box image. this is the bounding box, expanded by 25% on #: each side (as a fraction of the bbox width,height), and then the smaller #: dimension is expanded to as quare. image_pbox = models.ImageField( upload_to='pbox', blank=True, max_length=255, storage=STORAGE) image_pbox_300 = ImageSpecField( [ResizeToFit(300, 300)], source='image_pbox', format='JPEG', options={'quality': 90}, cachefile_storage=STORAGE) image_pbox_512 = ImageSpecField( [ResizeToFit(512, 512)], source='image_pbox', format='JPEG', options={'quality': 90}, cachefile_storage=STORAGE) image_pbox_1024 = ImageSpecField( [ResizeToFit(1024, 1024)], source='image_pbox', format='JPEG', options={'quality': 90}, cachefile_storage=STORAGE) # pbox width/height aspect ratio (as a ratio of pixel lengths) pbox_aspect_ratio = models.FloatField(null=True, blank=True) # json-encoded array [min x, min y, max x, max y] indicating the position # of the padded box. as usual, positions are specified as fractions of # width and height. pbox = models.TextField(blank=True) ## The THREE.js vertices are re-normalized so that the aspect ratio is ## correct. The x-coordinate is now in units of height, not width. ## THREE.js json file #three_js = models.FileField( #upload_to='three', blank=True, max_length=255) ## THREE.js buffer file #three_bin = models.FileField( #upload_to='three', blank=True, max_length=255) # approximate area of the rectified texture (in pixels) rectified_area = models.FloatField(null=True, blank=True) # dominant color of this shape dominant_r = models.FloatField(null=True, blank=True) dominant_g = models.FloatField(null=True, blank=True) dominant_b = models.FloatField(null=True, blank=True) # top 4 dominant colors written as #rrggbb (for easy HTML viewing) # (in decreasing order of frequency) dominant_rgb0 = models.CharField(max_length=7, blank=True, default='') dominant_rgb1 = models.CharField(max_length=7, blank=True, default='') dominant_rgb2 = models.CharField(max_length=7, blank=True, default='') dominant_rgb3 = models.CharField(max_length=7, blank=True, default='') # difference between top two colors dominant_delta = models.FloatField(null=True, blank=True) def has_fov(self): return self.photo.fov > 0 def publishable(self): return self.photo.publishable() def image_pbox_height(self, width): """ Returns the height of image_pbox_<width> """ return min(width, width / self.pbox_aspect_ratio) def label_pos_x_scaled(self): """ Returns the label position normalized by height instead of width """ return self.label_pos_x * self.photo.aspect_ratio def label_pos_2_y_512(self): return self.label_pos_y + 1.25 * self.photo.font_size_512() # helpers for templates def image_pbox_height_1024(self): return self.image_pbox_height(1024) def image_pbox_height_512(self): return self.image_pbox_height(512) def save(self, *args, **kwargs): # compute counts: if not self.num_vertices: self.num_vertices = len(self.vertices.split(',')) // 2 if not self.num_triangles: self.num_triangles = len(self.triangles.split(',')) // 3 if not self.num_segments: self.num_segments = len(self.segments.split(',')) // 2 if not self.area: from shapes.utils import complex_polygon_area self.area = complex_polygon_area(self.vertices, self.triangles) if not self.pixel_area: self.pixel_area = (self.area * self.photo.image_orig.width * self.photo.image_orig.height) if not self.synthetic: self.synthetic = self.photo.synthetic if not self.label_pos_x or not self.label_pos_y: from shapes.utils import update_shape_label_pos update_shape_label_pos(self, save=False) thumbs_dirty = (not self.id) # compute cropped image synchronously, before saving # (this way the shape only shows up after all thumbs are available) if not self.image_crop or not self.image_bbox: from shapes.utils import update_shape_image_crop update_shape_image_crop(self, save=False) thumbs_dirty = True if not self.image_pbox: from shapes.utils import update_shape_image_pbox update_shape_image_pbox(self, save=False) thumbs_dirty = True #if not self.three_js or not self.three_bin: #from shapes.utils import update_shape_three #update_shape_three(self, save=False) #if not self.dominant_rgb0: if thumbs_dirty: from shapes.utils import update_shape_dominant_rgb update_shape_dominant_rgb(self, save=False) if (not self.dominant_delta and self.dominant_rgb0 and self.dominant_rgb1): from shapes.utils import update_shape_dominant_delta update_shape_dominant_delta(self, save=False) super(Shape, self).save(*args, **kwargs) def render_full_complex_polygon_mask( self, width=None, height=None, inverted=False): """ Returns a black-and-white PIL image (mode ``1``) the same size as the original photo (unless ``width`` and ``height`` are specified. Pixels inside the polygon are ``1`` and pixels outside are ``0`` (unless ``inverted=True``). :param width: width in pixels, or if ``None``, use ``self.photo.orig_width``. :param height: width in pixels, or if ``None``, use ``self.photo.orig_height``. :param inverted: if ``True``, swap ``0`` and ``1`` in the output. """ from shapes.utils import render_full_complex_polygon_mask return render_full_complex_polygon_mask( vertices=self.vertices, triangles=self.triangles, width=width if width else self.photo.orig_width, height=height if height else self.photo.orig_height, inverted=inverted) # temporary hack def is_kitchen(self): return (self.photo.scene_category.name == u'kitchen') # temporary hack def is_living_room(self): return (self.photo.scene_category.name == u'living room') # deprecated -- delete at some point def area_pixels(self): return (self.area * self.photo.image_orig.width * self.photo.image_orig.height) def __unicode__(self): return 'Shape (%s v)' % self.num_vertices def segments_svg_path(self): """ Returns all line segments as SVG path data """ verts = self.vertices.split(',') # leave as string segs = [int(v) for v in self.segments.split(',')] data = [] for i in xrange(0, len(segs), 2): v0 = 2 * segs[i] v1 = 2 * segs[i + 1] data.append(u"M%s,%sL%s,%s" % ( verts[v0], verts[v0 + 1], verts[v1], verts[v1 + 1], )) return u"".join(data) def triangles_svg_path(self): """ Returns all triangles as SVG path data """ verts = self.vertices.split(',') # leave as string tris = [int(v) for v in self.triangles.split(',')] data = [] for i in xrange(0, len(tris), 3): v0 = 2 * tris[i] v1 = 2 * tris[i + 1] v2 = 2 * tris[i + 2] data.append(u"M%s,%sL%s,%sL%s,%sz" % ( verts[v0], verts[v0 + 1], verts[v1], verts[v1 + 1], verts[v2], verts[v2 + 1], )) return u"".join(data) def pbox_view_box(self): """ Returns the padded box as the tuple ``(min_x, min_y, width, height)`` """ pbox = json.loads(self.pbox) return (pbox[0], pbox[1], pbox[2] - pbox[0], pbox[3] - pbox[0]) def pbox_svg_transform(self): return "scale(%s,1) %s" % ( self.pbox_aspect_ratio, bbox_svg_transform(json.loads(self.pbox)), ) #def shape_svg_url_large(self): #from shapes.utils import material_shape_svg_url_large #return material_shape_svg_url_large(self) #def shape_svg_url_small(self): #from shapes.utils import material_shape_svg_url_small #return material_shape_svg_url_small(self) def get_entry_dict(self): """ Return a dictionary of this model containing just the fields needed for javascript rendering. """ # generating thumbnail URLs is slow, so only generate the ones # that will definitely be used. ret = { 'id': self.id, 'vertices': self.vertices, 'triangles': self.triangles, 'segments': self.segments, 'photo': self.photo.get_entry_dict(), } if self.dominant_rgb0: ret['dominant_rgb0'] = self.dominant_rgb0 #if self.image_pbox: #ret['pbox'] = self.pbox #ret['image_pbox'] = { #'300': self.image_pbox_300.url, #'512': self.image_pbox_512.url, #'1024': self.image_pbox_1024.url, #'orig': self.image_pbox.url, #} if self.image_bbox: ret['image_bbox'] = { #'512': self.image_bbox_512.url, '1024': self.image_bbox_1024.url, #'orig': self.image_bbox.url, } return ret def mark_invalid(self, *args, **kwargs): self.correct = False super(Shape, self).mark_invalid(*args, **kwargs) class Meta: abstract = True ordering = ['-num_vertices', '-time_ms']
class ProductImage(models.Model): product = models.ForeignKey(Product, verbose_name='product', related_name='images', on_delete=models.CASCADE) sort = models.PositiveSmallIntegerField('sorting', default=1) image = models.ImageField('picture', upload_to=upload_to('products')) image_admin = ImageSpecField(source='image', processors=[ResizeToCover(100, 100)], format='JPEG', options={'quality': 90}) image_banner = ImageSpecField(source='image', processors=[ ResizeToFit(250, 250), ResizeCanvas(250, 250, anchor=Anchor.CENTER) ], format='PNG') image_product = ImageSpecField(source='image', processors=[ ResizeToFit(280, 280), ResizeCanvas(280, 280, anchor=Anchor.CENTER) ], format='PNG') image_category = ImageSpecField(source='image', processors=[ ResizeToFit(160, 160), ResizeCanvas(160, 160, anchor=Anchor.CENTER) ], format='PNG') image_list = ImageSpecField(source='image', processors=[ ResizeToFit(160, 160), ResizeCanvas(160, 160, anchor=Anchor.CENTER) ], format='PNG') image_cart = ImageSpecField(source='image', processors=[ ResizeToFit(86, 86), ResizeCanvas(86, 86, anchor=Anchor.CENTER) ], format='PNG') class Meta: verbose_name = 'product image' verbose_name_plural = 'product images' ordering = ('sort', )