Format code using black

This commit is contained in:
Alexandre Iooss 2022-03-02 21:23:40 +01:00
parent 2ad0c8dbc7
commit 59136050fb
14 changed files with 809 additions and 413 deletions

View file

@ -25,31 +25,37 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from PIL import Image, ImageFile, ImageFilter
logger = logging.getLogger('photologue.models')
logger = logging.getLogger("photologue.models")
# Default limit for gallery.latest
LATEST_LIMIT = getattr(settings, 'PHOTOLOGUE_GALLERY_LATEST_LIMIT', None)
LATEST_LIMIT = getattr(settings, "PHOTOLOGUE_GALLERY_LATEST_LIMIT", None)
# max_length setting for the ImageModel ImageField
IMAGE_FIELD_MAX_LENGTH = getattr(settings, 'PHOTOLOGUE_IMAGE_FIELD_MAX_LENGTH', 100)
IMAGE_FIELD_MAX_LENGTH = getattr(settings, "PHOTOLOGUE_IMAGE_FIELD_MAX_LENGTH", 100)
# Modify image file buffer size.
ImageFile.MAXBLOCK = getattr(settings, 'PHOTOLOGUE_MAXBLOCK', 256 * 2 ** 10)
ImageFile.MAXBLOCK = getattr(settings, "PHOTOLOGUE_MAXBLOCK", 256 * 2 ** 10)
# Look for user function to define file paths
PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None)
PHOTOLOGUE_PATH = getattr(settings, "PHOTOLOGUE_PATH", None)
if PHOTOLOGUE_PATH is not None:
if callable(PHOTOLOGUE_PATH):
get_storage_path = PHOTOLOGUE_PATH
else:
parts = PHOTOLOGUE_PATH.split('.')
module_name = '.'.join(parts[:-1])
parts = PHOTOLOGUE_PATH.split(".")
module_name = ".".join(parts[:-1])
module = import_module(module_name)
get_storage_path = getattr(module, parts[-1])
else:
def get_storage_path(instance, filename):
fn = unicodedata.normalize('NFKD', force_str(filename)).encode('ascii', 'ignore').decode('ascii')
return os.path.join('photos', fn)
fn = (
unicodedata.normalize("NFKD", force_str(filename))
.encode("ascii", "ignore")
.decode("ascii")
)
return os.path.join("photos", fn)
# Exif Orientation values
# Value 0thRow 0thColumn
@ -73,42 +79,47 @@ IMAGE_EXIF_ORIENTATION_MAP = {
# Quality options for JPEG images
JPEG_QUALITY_CHOICES = (
(30, _('Very Low')),
(40, _('Low')),
(50, _('Medium-Low')),
(60, _('Medium')),
(70, _('Medium-High')),
(80, _('High')),
(90, _('Very High')),
(30, _("Very Low")),
(40, _("Low")),
(50, _("Medium-Low")),
(60, _("Medium")),
(70, _("Medium-High")),
(80, _("High")),
(90, _("Very High")),
)
# choices for new crop_anchor field in Photo
CROP_ANCHOR_CHOICES = (
('top', _('Top')),
('right', _('Right')),
('bottom', _('Bottom')),
('left', _('Left')),
('center', _('Center (Default)')),
("top", _("Top")),
("right", _("Right")),
("bottom", _("Bottom")),
("left", _("Left")),
("center", _("Center (Default)")),
)
IMAGE_TRANSPOSE_CHOICES = (
('FLIP_LEFT_RIGHT', _('Flip left to right')),
('FLIP_TOP_BOTTOM', _('Flip top to bottom')),
('ROTATE_90', _('Rotate 90 degrees counter-clockwise')),
('ROTATE_270', _('Rotate 90 degrees clockwise')),
('ROTATE_180', _('Rotate 180 degrees')),
("FLIP_LEFT_RIGHT", _("Flip left to right")),
("FLIP_TOP_BOTTOM", _("Flip top to bottom")),
("ROTATE_90", _("Rotate 90 degrees counter-clockwise")),
("ROTATE_270", _("Rotate 90 degrees clockwise")),
("ROTATE_180", _("Rotate 180 degrees")),
)
# Prepare a list of image filters
filter_names = []
for n in dir(ImageFilter):
klass = getattr(ImageFilter, n)
if isclass(klass) and issubclass(klass, ImageFilter.BuiltinFilter) and \
hasattr(klass, 'name'):
if (
isclass(klass)
and issubclass(klass, ImageFilter.BuiltinFilter)
and hasattr(klass, "name")
):
filter_names.append(klass.__name__)
IMAGE_FILTERS_HELP_TEXT = _('Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE"'
'. Image filters will be applied in order. The following filters are available: %s.'
% (', '.join(filter_names)))
IMAGE_FILTERS_HELP_TEXT = _(
'Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE"'
". Image filters will be applied in order. The following filters are available: %s."
% (", ".join(filter_names))
)
size_method_map = {}
@ -119,22 +130,22 @@ class TagField(models.CharField):
"""
def __init__(self, **kwargs):
default_kwargs = {'max_length': 255, 'blank': True}
default_kwargs = {"max_length": 255, "blank": True}
default_kwargs.update(kwargs)
super().__init__(**default_kwargs)
def get_internal_type(self):
return 'CharField'
return "CharField"
class Gallery(models.Model):
title = models.CharField(_('title'),
max_length=250,
unique=True)
slug = models.SlugField(_('title slug'),
unique=True,
max_length=250,
help_text=_('A "slug" is a unique URL-friendly title for an object.'))
title = models.CharField(_("title"), max_length=250, unique=True)
slug = models.SlugField(
_("title slug"),
unique=True,
max_length=250,
help_text=_('A "slug" is a unique URL-friendly title for an object.'),
)
date_start = models.DateField(
default=now,
verbose_name=_("start date"),
@ -144,30 +155,31 @@ class Gallery(models.Model):
null=True,
verbose_name=_("end date"),
)
description = models.TextField(_('description'),
blank=True)
description = models.TextField(_("description"), blank=True)
tags = models.ManyToManyField(
'photologue.Tag',
related_name='galleries',
verbose_name=_('tags'),
"photologue.Tag",
related_name="galleries",
verbose_name=_("tags"),
blank=True,
)
photos = models.ManyToManyField(
"photologue.Photo",
related_name="galleries",
verbose_name=_("photos"),
blank=True,
)
photos = models.ManyToManyField('photologue.Photo',
related_name='galleries',
verbose_name=_('photos'),
blank=True)
class Meta:
ordering = ['-date_start']
get_latest_by = 'date_start'
verbose_name = _('gallery')
verbose_name_plural = _('galleries')
ordering = ["-date_start"]
get_latest_by = "date_start"
verbose_name = _("gallery")
verbose_name_plural = _("galleries")
def __str__(self):
return f"{ self.title } ({self.date_start})"
def get_absolute_url(self):
return reverse('photologue:pl-gallery', args=[self.slug])
return reverse("photologue:pl-gallery", args=[self.slug])
def sample(self, public=True):
"""Return a sample of photos, ordered at random."""
@ -191,26 +203,28 @@ class Gallery(models.Model):
"""Return a count of private photos in this gallery."""
return self.photos.filter(is_public=False).count()
photo_count.short_description = _('count')
photo_private_count.short_description = _('private count')
photo_count.short_description = _("count")
photo_private_count.short_description = _("private count")
class ImageModel(models.Model):
image = models.ImageField(_('image'),
max_length=IMAGE_FIELD_MAX_LENGTH,
upload_to=get_storage_path)
date_taken = models.DateTimeField(_('date taken'),
null=True,
blank=True,
help_text=_('Date image was taken; is obtained from the image EXIF data.'))
view_count = models.PositiveIntegerField(_('view count'),
default=0,
editable=False)
crop_from = models.CharField(_('crop from'),
blank=True,
max_length=10,
default='center',
choices=CROP_ANCHOR_CHOICES)
image = models.ImageField(
_("image"), max_length=IMAGE_FIELD_MAX_LENGTH, upload_to=get_storage_path
)
date_taken = models.DateTimeField(
_("date taken"),
null=True,
blank=True,
help_text=_("Date image was taken; is obtained from the image EXIF data."),
)
view_count = models.PositiveIntegerField(_("view count"), default=0, editable=False)
crop_from = models.CharField(
_("crop from"),
blank=True,
max_length=10,
default="center",
choices=CROP_ANCHOR_CHOICES,
)
class Meta:
abstract = True
@ -220,38 +234,44 @@ class ImageModel(models.Model):
if file:
tags = exifread.process_file(file)
else:
with self.image.storage.open(self.image.name, 'rb') as file:
with self.image.storage.open(self.image.name, "rb") as file:
tags = exifread.process_file(file, details=False)
return tags
except Exception:
return {}
def admin_thumbnail(self):
func = getattr(self, 'get_admin_thumbnail_url', None)
func = getattr(self, "get_admin_thumbnail_url", None)
if func is None:
return _('An "admin_thumbnail" photo size has not been defined.')
else:
if hasattr(self, 'get_absolute_url'):
return mark_safe('<a href="{}"><img src="{}"></a>'.format(self.get_absolute_url(), func()))
if hasattr(self, "get_absolute_url"):
return mark_safe(
'<a href="{}"><img src="{}"></a>'.format(
self.get_absolute_url(), func()
)
)
else:
return mark_safe('<a href="{}"><img src="{}"></a>'.format(self.image.url, func()))
return mark_safe(
'<a href="{}"><img src="{}"></a>'.format(self.image.url, func())
)
admin_thumbnail.short_description = _('Thumbnail')
admin_thumbnail.short_description = _("Thumbnail")
admin_thumbnail.allow_tags = True
def cache_path(self):
return os.path.join(os.path.dirname(self.image.name), "cache")
def cache_url(self):
return '/'.join([os.path.dirname(self.image.url), "cache"])
return "/".join([os.path.dirname(self.image.url), "cache"])
def image_filename(self):
return os.path.basename(force_str(self.image.name))
def _get_filename_for_size(self, size):
size = getattr(size, 'name', size)
size = getattr(size, "name", size)
base, ext = os.path.splitext(self.image_filename())
return ''.join([base, '_', size, ext])
return "".join([base, "_", size, ext])
def _get_size_photosize(self, size):
return PhotoSizeCache().sizes.get(size)
@ -261,8 +281,9 @@ class ImageModel(models.Model):
if not self.size_exists(photosize):
self.create_size(photosize)
try:
return Image.open(self.image.storage.open(
self._get_size_filename(size))).size
return Image.open(
self.image.storage.open(self._get_size_filename(size))
).size
except Exception:
return None
@ -272,14 +293,18 @@ class ImageModel(models.Model):
self.create_size(photosize)
if photosize.increment_count:
self.increment_count()
return '/'.join([
self.cache_url(),
filepath_to_uri(self._get_filename_for_size(photosize.name))])
return "/".join(
[
self.cache_url(),
filepath_to_uri(self._get_filename_for_size(photosize.name)),
]
)
def _get_size_filename(self, size):
photosize = PhotoSizeCache().sizes.get(size)
return smart_str(os.path.join(self.cache_path(),
self._get_filename_for_size(photosize.name)))
return smart_str(
os.path.join(self.cache_path(), self._get_filename_for_size(photosize.name))
)
def increment_count(self):
self.view_count += 1
@ -291,7 +316,7 @@ class ImageModel(models.Model):
init_size_method_map()
di = size_method_map.get(name, None)
if di is not None:
result = partial(getattr(self, di['base_name']), di['size'])
result = partial(getattr(self, di["base_name"]), di["size"])
setattr(self, name, result)
return result
else:
@ -313,38 +338,45 @@ class ImageModel(models.Model):
new_width, new_height = photosize.size
if photosize.crop:
ratio = max(float(new_width) / cur_width, float(new_height) / cur_height)
x = (cur_width * ratio)
y = (cur_height * ratio)
x = cur_width * ratio
y = cur_height * ratio
xd = abs(new_width - x)
yd = abs(new_height - y)
x_diff = int(xd / 2)
y_diff = int(yd / 2)
if self.crop_from == 'top':
if self.crop_from == "top":
box = (int(x_diff), 0, int(x_diff + new_width), new_height)
elif self.crop_from == 'left':
elif self.crop_from == "left":
box = (0, int(y_diff), new_width, int(y_diff + new_height))
elif self.crop_from == 'bottom':
elif self.crop_from == "bottom":
# y - yd = new_height
box = (int(x_diff), int(yd), int(x_diff + new_width), int(y))
elif self.crop_from == 'right':
elif self.crop_from == "right":
# x - xd = new_width
box = (int(xd), int(y_diff), int(x), int(y_diff + new_height))
else:
box = (int(x_diff), int(y_diff), int(x_diff + new_width), int(y_diff + new_height))
box = (
int(x_diff),
int(y_diff),
int(x_diff + new_width),
int(y_diff + new_height),
)
im = im.resize((int(x), int(y)), Image.ANTIALIAS).crop(box)
else:
if not new_width == 0 and not new_height == 0:
ratio = min(float(new_width) / cur_width,
float(new_height) / cur_height)
ratio = min(
float(new_width) / cur_width, float(new_height) / cur_height
)
else:
if new_width == 0:
ratio = float(new_height) / cur_height
else:
ratio = float(new_width) / cur_width
new_dimensions = (int(round(cur_width * ratio)),
int(round(cur_height * ratio)))
if new_dimensions[0] > cur_width or \
new_dimensions[1] > cur_height:
new_dimensions = (
int(round(cur_width * ratio)),
int(round(cur_height * ratio)),
)
if new_dimensions[0] > cur_width or new_dimensions[1] > cur_height:
if not photosize.upscale:
return im
im = im.resize(new_dimensions, Image.ANTIALIAS)
@ -360,10 +392,16 @@ class ImageModel(models.Model):
# Save the original format
im_format = im.format
# Rotate if found & necessary
if 'Image Orientation' in self.exif() and \
self.exif().get('Image Orientation').values[0] in IMAGE_EXIF_ORIENTATION_MAP:
if (
"Image Orientation" in self.exif()
and self.exif().get("Image Orientation").values[0]
in IMAGE_EXIF_ORIENTATION_MAP
):
im = im.transpose(
IMAGE_EXIF_ORIENTATION_MAP[self.exif().get('Image Orientation').values[0]])
IMAGE_EXIF_ORIENTATION_MAP[
self.exif().get("Image Orientation").values[0]
]
)
# Resize/crop image
if (im.size != photosize.size and photosize.size != (0, 0)) or recreate:
im = self.resize_image(im, photosize)
@ -371,14 +409,13 @@ class ImageModel(models.Model):
im_filename = getattr(self, "get_%s_filename" % photosize.name)()
try:
buffer = BytesIO()
if im_format != 'JPEG':
if im_format != "JPEG":
im.save(buffer, im_format)
else:
# Issue #182 - test fix from https://github.com/bashu/django-watermark/issues/31
if im.mode.endswith('A'):
if im.mode.endswith("A"):
im = im.convert(im.mode[:-1])
im.save(buffer, 'JPEG', quality=int(photosize.quality),
optimize=True)
im.save(buffer, "JPEG", quality=int(photosize.quality), optimize=True)
buffer_contents = ContentFile(buffer.getvalue())
self.image.storage.save(im_filename, buffer_contents)
except OSError as e:
@ -411,7 +448,7 @@ class ImageModel(models.Model):
self._old_image = self.image
def save(self, *args, **kwargs):
recreate = kwargs.pop('recreate', False)
recreate = kwargs.pop("recreate", False)
image_has_changed = False
if self._get_pk_val() and (self._old_image != self.image):
image_has_changed = True
@ -423,26 +460,39 @@ class ImageModel(models.Model):
self.image = self._old_image
self.clear_cache()
self.image = new_image # Back to the new image.
self._old_image.storage.delete(self._old_image.name) # Delete (old) base image.
self._old_image.storage.delete(
self._old_image.name
) # Delete (old) base image.
if self.date_taken is None or image_has_changed:
# Attempt to get the date the photo was taken from the EXIF data.
try:
exif_date = self.exif(self.image.file).get('EXIF DateTimeOriginal', None)
exif_date = self.exif(self.image.file).get(
"EXIF DateTimeOriginal", None
)
if exif_date is not None:
d, t = exif_date.values.split()
year, month, day = d.split(':')
hour, minute, second = t.split(':')
self.date_taken = datetime(int(year), int(month), int(day),
int(hour), int(minute), int(second))
year, month, day = d.split(":")
hour, minute, second = t.split(":")
self.date_taken = datetime(
int(year),
int(month),
int(day),
int(hour),
int(minute),
int(second),
)
except Exception:
logger.error('Failed to read EXIF DateTimeOriginal', exc_info=True)
logger.error("Failed to read EXIF DateTimeOriginal", exc_info=True)
super().save(*args, **kwargs)
self.pre_cache(recreate)
def delete(self):
assert self._get_pk_val() is not None, \
"%s object can't be deleted because its %s attribute is set to None." % \
(self._meta.object_name, self._meta.pk.attname)
assert (
self._get_pk_val() is not None
), "%s object can't be deleted because its %s attribute is set to None." % (
self._meta.object_name,
self._meta.pk.attname,
)
self.clear_cache()
# Files associated to a FileField have to be manually deleted:
# https://docs.djangoproject.com/en/dev/releases/1.3/#deleting-a-model-doesn-t-delete-associated-files
@ -454,17 +504,15 @@ class ImageModel(models.Model):
class Photo(ImageModel):
title = models.CharField(_('title'),
max_length=250,
unique=True)
slug = models.SlugField(_('slug'),
unique=True,
max_length=250,
help_text=_('A "slug" is a unique URL-friendly title for an object.'))
caption = models.TextField(_('caption'),
blank=True)
date_added = models.DateTimeField(_('date added'),
default=now)
title = models.CharField(_("title"), max_length=250, unique=True)
slug = models.SlugField(
_("slug"),
unique=True,
max_length=250,
help_text=_('A "slug" is a unique URL-friendly title for an object.'),
)
caption = models.TextField(_("caption"), blank=True)
date_added = models.DateTimeField(_("date added"), default=now)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
@ -475,15 +523,17 @@ class Photo(ImageModel):
blank=True,
verbose_name=_("license"),
)
is_public = models.BooleanField(_('is public'),
default=True,
help_text=_('Public photographs will be displayed in the default views.'))
is_public = models.BooleanField(
_("is public"),
default=True,
help_text=_("Public photographs will be displayed in the default views."),
)
class Meta:
# We do not have a reliable date for ordering, so let's use
# the title which is incremented by most cameras
ordering = ['title']
get_latest_by = 'date_added'
ordering = ["title"]
get_latest_by = "date_added"
verbose_name = _("photo")
verbose_name_plural = _("photos")
@ -502,7 +552,7 @@ class Photo(ImageModel):
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('photologue:pl-photo', args=[self.pk])
return reverse("photologue:pl-photo", args=[self.pk])
def public_galleries(self):
"""Return the public galleries to which this photo belongs."""
@ -513,10 +563,10 @@ class Photo(ImageModel):
We assume that the gallery and all its photos are on the same site.
"""
if not self.is_public:
raise ValueError('Cannot determine neighbours of a non-public photo.')
raise ValueError("Cannot determine neighbours of a non-public photo.")
photos = gallery.photos.filter(is_public=True)
if self not in photos:
raise ValueError('Photo does not belong to gallery.')
raise ValueError("Photo does not belong to gallery.")
previous = None
for photo in photos:
if photo == self:
@ -528,10 +578,10 @@ class Photo(ImageModel):
We assume that the gallery and all its photos are on the same site.
"""
if not self.is_public:
raise ValueError('Cannot determine neighbours of a non-public photo.')
raise ValueError("Cannot determine neighbours of a non-public photo.")
photos = gallery.photos.filter(is_public=True)
if self not in photos:
raise ValueError('Photo does not belong to gallery.')
raise ValueError("Photo does not belong to gallery.")
matched = False
for photo in photos:
if matched:
@ -546,51 +596,79 @@ class PhotoSize(models.Model):
so the name has to follow the same restrictions as any Python method name,
e.g. no spaces or non-ascii characters."""
name = models.CharField(_('name'),
max_length=40,
unique=True,
help_text=_(
'Photo size name should contain only letters, numbers and underscores. Examples: '
'"thumbnail", "display", "small", "main_page_widget".'),
validators=[RegexValidator(regex='^[a-z0-9_]+$',
message='Use only plain lowercase letters (ASCII), numbers and '
'underscores.'
)]
)
width = models.PositiveIntegerField(_('width'),
default=0,
help_text=_(
'If width is set to "0" the image will be scaled to the supplied height.'))
height = models.PositiveIntegerField(_('height'),
default=0,
help_text=_(
'If height is set to "0" the image will be scaled to the supplied width'))
quality = models.PositiveIntegerField(_('quality'),
choices=JPEG_QUALITY_CHOICES,
default=70,
help_text=_('JPEG image quality.'))
upscale = models.BooleanField(_('upscale images?'),
default=False,
help_text=_('If selected the image will be scaled up if necessary to fit the '
'supplied dimensions. Cropped sizes will be upscaled regardless of this '
'setting.')
)
crop = models.BooleanField(_('crop to fit?'),
default=False,
help_text=_('If selected the image will be scaled and cropped to fit the supplied '
'dimensions.'))
pre_cache = models.BooleanField(_('pre-cache?'),
default=False,
help_text=_('If selected this photo size will be pre-cached as photos are added.'))
increment_count = models.BooleanField(_('increment view count?'),
default=False,
help_text=_('If selected the image\'s "view_count" will be incremented when '
'this photo size is displayed.'))
name = models.CharField(
_("name"),
max_length=40,
unique=True,
help_text=_(
"Photo size name should contain only letters, numbers and underscores. Examples: "
'"thumbnail", "display", "small", "main_page_widget".'
),
validators=[
RegexValidator(
regex="^[a-z0-9_]+$",
message="Use only plain lowercase letters (ASCII), numbers and "
"underscores.",
)
],
)
width = models.PositiveIntegerField(
_("width"),
default=0,
help_text=_(
'If width is set to "0" the image will be scaled to the supplied height.'
),
)
height = models.PositiveIntegerField(
_("height"),
default=0,
help_text=_(
'If height is set to "0" the image will be scaled to the supplied width'
),
)
quality = models.PositiveIntegerField(
_("quality"),
choices=JPEG_QUALITY_CHOICES,
default=70,
help_text=_("JPEG image quality."),
)
upscale = models.BooleanField(
_("upscale images?"),
default=False,
help_text=_(
"If selected the image will be scaled up if necessary to fit the "
"supplied dimensions. Cropped sizes will be upscaled regardless of this "
"setting."
),
)
crop = models.BooleanField(
_("crop to fit?"),
default=False,
help_text=_(
"If selected the image will be scaled and cropped to fit the supplied "
"dimensions."
),
)
pre_cache = models.BooleanField(
_("pre-cache?"),
default=False,
help_text=_(
"If selected this photo size will be pre-cached as photos are added."
),
)
increment_count = models.BooleanField(
_("increment view count?"),
default=False,
help_text=_(
'If selected the image\'s "view_count" will be incremented when '
"this photo size is displayed."
),
)
class Meta:
ordering = ['width', 'height']
verbose_name = _('photo size')
verbose_name_plural = _('photo sizes')
ordering = ["width", "height"]
verbose_name = _("photo size")
verbose_name_plural = _("photo sizes")
def __str__(self):
return self.name
@ -607,7 +685,10 @@ class PhotoSize(models.Model):
if self.crop is True:
if self.width == 0 or self.height == 0:
raise ValidationError(
_("Can only crop photos if both width and height dimensions are set."))
_(
"Can only crop photos if both width and height dimensions are set."
)
)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
@ -615,8 +696,12 @@ class PhotoSize(models.Model):
self.clear_cache()
def delete(self):
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." \
% (self._meta.object_name, self._meta.pk.attname)
assert (
self._get_pk_val() is not None
), "%s object can't be deleted because its %s attribute is set to None." % (
self._meta.object_name,
self._meta.pk.attname,
)
self.clear_cache()
super().delete()
@ -648,33 +733,41 @@ class PhotoSizeCache:
def init_size_method_map():
global size_method_map
for size in PhotoSizeCache().sizes.keys():
size_method_map['get_%s_size' % size] = \
{'base_name': '_get_size_size', 'size': size}
size_method_map['get_%s_photosize' % size] = \
{'base_name': '_get_size_photosize', 'size': size}
size_method_map['get_%s_url' % size] = \
{'base_name': '_get_size_url', 'size': size}
size_method_map['get_%s_filename' % size] = \
{'base_name': '_get_size_filename', 'size': size}
size_method_map["get_%s_size" % size] = {
"base_name": "_get_size_size",
"size": size,
}
size_method_map["get_%s_photosize" % size] = {
"base_name": "_get_size_photosize",
"size": size,
}
size_method_map["get_%s_url" % size] = {
"base_name": "_get_size_url",
"size": size,
}
size_method_map["get_%s_filename" % size] = {
"base_name": "_get_size_filename",
"size": size,
}
class Tag(models.Model):
name = models.CharField(
max_length=250,
unique=True,
verbose_name=_('name'),
verbose_name=_("name"),
)
slug = models.SlugField(
unique=True,
max_length=250,
verbose_name=_('slug'),
verbose_name=_("slug"),
help_text=_('A "slug" is a unique URL-friendly title for an object.'),
)
class Meta:
ordering = ['name']
verbose_name = _('tag')
verbose_name_plural = _('tags')
ordering = ["name"]
verbose_name = _("tag")
verbose_name_plural = _("tags")
def __str__(self):
return self.name