Keep media folder clean (#28)

* Reorg roster, portal and bookshelf media
* Extend media reorg to consists
* Delete roster and bookshelf images on delte.
   Do not delete others data that might be dedup! 
* Bump version
This commit is contained in:
2023-10-31 11:16:55 +01:00
committed by GitHub
parent 416ca5bbc6
commit 830da80302
12 changed files with 302 additions and 12 deletions

View File

@@ -0,0 +1,49 @@
# Generated by Django 4.2.6 on 2023-10-30 13:16
import os
import sys
import shutil
import ram.utils
import bookshelf.models
from django.db import migrations, models
from django.conf import settings
def move_images(apps, schema_editor):
sys.stdout.write("\n Processing files. Please await...")
for r in bookshelf.models.BookImage.objects.all():
fname = os.path.basename(r.image.path)
new_image = bookshelf.models.book_image_upload(r, fname)
new_path = os.path.join(settings.MEDIA_ROOT, new_image)
os.makedirs(os.path.dirname(new_path), exist_ok=True)
try:
shutil.move(r.image.path, new_path)
except FileNotFoundError:
sys.stderr.write(" !! FileNotFoundError: {}\n".format(new_image))
pass
r.image.name = new_image
r.save()
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0008_alter_author_options_alter_publisher_options"),
]
operations = [
migrations.AlterField(
model_name="bookimage",
name="image",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to=bookshelf.models.book_image_upload,
),
),
migrations.RunPython(
move_images,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -1,3 +1,5 @@
import os
import shutil
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
@@ -70,13 +72,31 @@ class Book(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("book", kwargs={"uuid": self.uuid}) return reverse("book", kwargs={"uuid": self.uuid})
def delete(self, *args, **kwargs):
shutil.rmtree(
os.path.join(
settings.MEDIA_ROOT, "images", "books", str(self.uuid)
),
ignore_errors=True
)
super(Book, self).delete(*args, **kwargs)
def book_image_upload(instance, filename):
return os.path.join(
"images",
"books",
str(instance.book.uuid),
filename
)
class BookImage(Image): class BookImage(Image):
book = models.ForeignKey( book = models.ForeignKey(
Book, on_delete=models.CASCADE, related_name="image" Book, on_delete=models.CASCADE, related_name="image"
) )
image = models.ImageField( image = models.ImageField(
upload_to="images/books/", # FIXME, find a better way to replace this upload_to=book_image_upload,
storage=DeduplicatedStorage, storage=DeduplicatedStorage,
null=True, null=True,
blank=True blank=True

View File

@@ -0,0 +1,51 @@
# Generated by Django 4.2.6 on 2023-10-31 09:41
import os
import sys
import shutil
import ram.utils
from django.conf import settings
from django.db import migrations, models
def move_images(apps, schema_editor):
sys.stdout.write("\n Processing files. Please await...")
model = apps.get_model("consist", "Consist")
for r in model.objects.all():
if not r.image: # exit the loop if there's no image
continue
fname = os.path.basename(r.image.path)
new_image = os.path.join("images", "consists", fname)
new_path = os.path.join(settings.MEDIA_ROOT, new_image)
os.makedirs(os.path.dirname(new_path), exist_ok=True)
try:
shutil.move(r.image.path, new_path)
except FileNotFoundError:
sys.stderr.write(" !! FileNotFoundError: {}\n".format(new_image))
pass
r.image.name = new_image
r.save()
class Migration(migrations.Migration):
dependencies = [
("consist", "0008_alter_consist_options"),
]
operations = [
migrations.AlterField(
model_name="consist",
name="image",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/consists",
),
),
migrations.RunPython(
move_images,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -1,3 +1,5 @@
import os
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
@@ -19,7 +21,10 @@ class Consist(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE) company = models.ForeignKey(Company, on_delete=models.CASCADE)
era = models.CharField(max_length=32, blank=True) era = models.CharField(max_length=32, blank=True)
image = models.ImageField( image = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True upload_to=os.path.join("images", "consists"),
storage=DeduplicatedStorage,
null=True,
blank=True,
) )
notes = RichTextUploadingField(blank=True) notes = RichTextUploadingField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True) creation_time = models.DateTimeField(auto_now_add=True)

View File

@@ -0,0 +1,80 @@
# Generated by Django 4.2.6 on 2023-10-30 13:16
import os
import sys
import shutil
import ram.utils
from django.conf import settings
from django.db import migrations, models
def move_images(apps, schema_editor):
fields = {
"Company": ["companies", "logo"],
"Decoder": ["decoders", "image"],
"Manufacturer": ["manufacturers", "logo"],
}
sys.stdout.write("\n Processing files. Please await...")
for m in fields.items():
model = apps.get_model("metadata", m[0])
for r in model.objects.all():
field = getattr(r, m[1][1])
if not field: # exit the loop if there's no image
continue
fname = os.path.basename(field.path)
new_image = os.path.join("images", m[1][0], fname)
new_path = os.path.join(settings.MEDIA_ROOT, new_image)
os.makedirs(os.path.dirname(new_path), exist_ok=True)
try:
shutil.move(field.path, new_path)
except FileNotFoundError:
sys.stderr.write(
" !! FileNotFoundError: {}\n".format(new_image)
)
pass
field.name = new_image
r.save()
class Migration(migrations.Migration):
dependencies = [
("metadata", "0014_alter_decoder_options_alter_tag_options"),
]
operations = [
migrations.AlterField(
model_name="company",
name="logo",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/companies",
),
),
migrations.AlterField(
model_name="decoder",
name="image",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/decoders",
),
),
migrations.AlterField(
model_name="manufacturer",
name="logo",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/manufacturers",
),
),
migrations.RunPython(
move_images,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -1,3 +1,4 @@
import os
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.conf import settings from django.conf import settings
@@ -28,7 +29,10 @@ class Manufacturer(models.Model):
) )
website = models.URLField(blank=True) website = models.URLField(blank=True)
logo = models.ImageField( logo = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True upload_to=os.path.join("images", "manufacturers"),
storage=DeduplicatedStorage,
null=True,
blank=True,
) )
class Meta: class Meta:
@@ -58,7 +62,10 @@ class Company(models.Model):
country = CountryField() country = CountryField()
freelance = models.BooleanField(default=False) freelance = models.BooleanField(default=False)
logo = models.ImageField( logo = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True upload_to=os.path.join("images", "companies"),
storage=DeduplicatedStorage,
null=True,
blank=True,
) )
class Meta: class Meta:
@@ -95,7 +102,10 @@ class Decoder(models.Model):
version = models.CharField(max_length=64, blank=True) version = models.CharField(max_length=64, blank=True)
sound = models.BooleanField(default=False) sound = models.BooleanField(default=False)
image = models.ImageField( image = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True upload_to=os.path.join("images", "decoders"),
storage=DeduplicatedStorage,
null=True,
blank=True,
) )
class Meta(object): class Meta(object):

View File

@@ -1,7 +1,7 @@
<div class="col"> <div class="col">
<div class="card shadow-sm"> <div class="card shadow-sm">
{% if d.item.image.exists %} {% if d.item.image.exists %}
<a href="{{d.item.get_absolute_url}}"><img class="card-img-top" src="{{ d.item.image.first.image.url }}" alt="{{ d }}"></a> <a href="{{d.item.get_absolute_url}}"><img class="card-img-top" src="{{ d.item.image.first.image.url }}" alt="{{ d.item }}"></a>
{% endif %} {% endif %}
<div class="card-body"> <div class="card-body">
<p class="card-text" style="position: relative;"> <p class="card-text" style="position: relative;">

View File

@@ -2,10 +2,10 @@
<div class="card shadow-sm"> <div class="card shadow-sm">
<a href="{{ d.item.get_absolute_url }}"> <a href="{{ d.item.get_absolute_url }}">
{% if d.item.image %} {% if d.item.image %}
<img class="card-img-top" src="{{ d.item.image.url }}" alt="{{ d }}"> <img class="card-img-top" src="{{ d.item.image.url }}" alt="{{ d.item }}">
{% else %} {% else %}
{% with d.item.consist_item.first.rolling_stock as r %} {% with d.item.consist_item.first.rolling_stock as r %}
<img class="card-img-top" src="{{ r.image.first.image.url }}" alt="{{ d }}"> <img class="card-img-top" src="{{ r.image.first.image.url }}" alt="{{ d.item }}">
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</a> </a>

View File

@@ -2,10 +2,10 @@
<div class="col"> <div class="col">
<div class="card shadow-sm"> <div class="card shadow-sm">
{% if d.item.image.exists %} {% if d.item.image.exists %}
<a href="{{d.item.get_absolute_url}}"><img class="card-img-top" src="{{ d.item.image.first.image.url }}" alt="{{ d }}"></a> <a href="{{d.item.get_absolute_url}}"><img class="card-img-top" src="{{ d.item.image.first.image.url }}" alt="{{ d.item }}"></a>
{% else %} {% else %}
<!-- Do not show the "Coming soon" image when running in a single card column mode (e.g. on mobile) --> <!-- Do not show the "Coming soon" image when running in a single card column mode (e.g. on mobile) -->
<a href="{{d.item.get_absolute_url}}"><img class="card-img-top d-none d-sm-block" src="{% static DEFAULT_CARD_IMAGE %}" alt="{{ d }}"></a> <a href="{{d.item.get_absolute_url}}"><img class="card-img-top d-none d-sm-block" src="{% static DEFAULT_CARD_IMAGE %}" alt="{{ d.item }}"></a>
{% endif %} {% endif %}
<div class="card-body"> <div class="card-body">
<p class="card-text" style="position: relative;"> <p class="card-text" style="position: relative;">

View File

@@ -1,4 +1,4 @@
from ram.utils import git_suffix from ram.utils import git_suffix
__version__ = "0.9.3" __version__ = "0.9.4"
__version__ += git_suffix(__file__) __version__ += git_suffix(__file__)

View File

@@ -0,0 +1,49 @@
# Generated by Django 4.2.6 on 2023-10-30 13:16
import os
import sys
import shutil
import ram.utils
import roster.models
from django.db import migrations, models
from django.conf import settings
def move_images(apps, schema_editor):
sys.stdout.write("\n Processing files. Please await...")
for r in roster.models.RollingStockImage.objects.all():
fname = os.path.basename(r.image.path)
new_image = roster.models.rolling_stock_image_upload(r, fname)
new_path = os.path.join(settings.MEDIA_ROOT, new_image)
os.makedirs(os.path.dirname(new_path), exist_ok=True)
try:
shutil.move(r.image.path, new_path)
except FileNotFoundError:
sys.stderr.write(" !! FileNotFoundError: {}\n".format(new_image))
pass
r.image.name = new_image
r.save()
class Migration(migrations.Migration):
dependencies = [
("roster", "0019_rollingstockdocument_private"),
]
operations = [
migrations.AlterField(
model_name="rollingstockimage",
name="image",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to=roster.models.rolling_stock_image_upload,
),
),
migrations.RunPython(
move_images,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -1,4 +1,6 @@
import os
import re import re
import shutil
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
@@ -8,7 +10,7 @@ from django.dispatch import receiver
from ckeditor_uploader.fields import RichTextUploadingField from ckeditor_uploader.fields import RichTextUploadingField
from ram.models import Document, Image, PropertyInstance from ram.models import Document, Image, PropertyInstance
from ram.utils import get_image_preview from ram.utils import DeduplicatedStorage
from metadata.models import ( from metadata.models import (
Scale, Scale,
Manufacturer, Manufacturer,
@@ -106,6 +108,15 @@ class RollingStock(models.Model):
def company(self): def company(self):
return str(self.rolling_class.company) return str(self.rolling_class.company)
def delete(self, *args, **kwargs):
shutil.rmtree(
os.path.join(
settings.MEDIA_ROOT, "images", "rollingstock", str(self.uuid)
),
ignore_errors=True
)
super(RollingStock, self).delete(*args, **kwargs)
@receiver(models.signals.pre_save, sender=RollingStock) @receiver(models.signals.pre_save, sender=RollingStock)
def pre_save_running_number(sender, instance, *args, **kwargs): def pre_save_running_number(sender, instance, *args, **kwargs):
@@ -126,10 +137,25 @@ class RollingStockDocument(Document):
unique_together = ("rolling_stock", "file") unique_together = ("rolling_stock", "file")
def rolling_stock_image_upload(instance, filename):
return os.path.join(
"images",
"rollingstock",
str(instance.rolling_stock.uuid),
filename
)
class RollingStockImage(Image): class RollingStockImage(Image):
rolling_stock = models.ForeignKey( rolling_stock = models.ForeignKey(
RollingStock, on_delete=models.CASCADE, related_name="image" RollingStock, on_delete=models.CASCADE, related_name="image"
) )
image = models.ImageField(
upload_to=rolling_stock_image_upload,
storage=DeduplicatedStorage,
null=True,
blank=True,
)
class RollingStockProperty(PropertyInstance): class RollingStockProperty(PropertyInstance):