1
0
Fork 0
mirror of https://github.com/Findus23/RPGnotes.git synced 2024-09-19 15:43:45 +02:00

better image and delete unused

This commit is contained in:
Lukas Winkler 2021-09-14 20:10:38 +02:00
parent 17c192a559
commit c9892a59a1
Signed by: lukas
GPG key ID: 54DE4D798D244853
10 changed files with 146 additions and 27 deletions

View file

@ -2,9 +2,10 @@
{% block content %} {% block content %}
<h1>{{ object }}</h1> <h1>{{ object }}</h1>
<h2>Players</h2>
<ul> <ul>
{% for user in users %} {% for user,characters in players.items %}
<li>{{ user }}</li> <li>{{ user }}{% if characters %} ({{ characters|join:", " }}){% endif %}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}

View file

@ -45,7 +45,12 @@ class CampaignDetailView(generic.DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CampaignDetailView, self).get_context_data(**kwargs) context = super(CampaignDetailView, self).get_context_data(**kwargs)
context["users"] = self.get_object().user_set.all() players = self.get_object().user_set.exclude(pk__in=[1, 2])
context["players"] = {}
player: TenantUser
for player in players:
context["players"][player] = player.characters.all()
return context return context

View file

@ -6,4 +6,4 @@ from characters.models import Character
class CharacterForm(ModelForm): class CharacterForm(ModelForm):
class Meta: class Meta:
model = Character model = Character
fields = ["name", "description_md", "subtitle", "player", "location", "color", "image"] fields = ["name", "description_md", "subtitle", "player", "location", "color", "token_image"]

View file

@ -9,6 +9,7 @@ from common.models import BaseModel, DescriptionModel
from locations.models import Location from locations.models import Location
from rpg_notes.settings import AUTH_USER_MODEL from rpg_notes.settings import AUTH_USER_MODEL
from utils.colors import get_random_color, is_bright_color from utils.colors import get_random_color, is_bright_color
from utils.random_filename import get_file_path
def validate_color_hex(value: str): def validate_color_hex(value: str):
@ -18,14 +19,16 @@ def validate_color_hex(value: str):
class Character(BaseModel, DescriptionModel): class Character(BaseModel, DescriptionModel):
subtitle = models.CharField(max_length=100, blank=True) subtitle = models.CharField(max_length=100, blank=True)
player = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.PROTECT, blank=True, null=True) player = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.PROTECT, blank=True, null=True,
related_name="characters")
# faction = models.ForeignKey(Faction, on_delete=models.PROTECT, blank=True, null=True) # faction = models.ForeignKey(Faction, on_delete=models.PROTECT, blank=True, null=True)
location = models.ForeignKey(Location, on_delete=models.PROTECT, blank=True, null=True) location = models.ForeignKey(Location, on_delete=models.PROTECT, blank=True, null=True)
color = models.CharField(max_length=7, default=get_random_color, validators=[ color = models.CharField(max_length=7, default=get_random_color, validators=[
MinLengthValidator(7), MinLengthValidator(7),
validate_color_hex validate_color_hex
]) ])
image = ImageField(upload_to="character_images", blank=True, null=True) token_image = ImageField(upload_to=get_file_path, blank=True, null=True)
large_image = ImageField(upload_to=get_file_path, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True) last_modified = models.DateTimeField(auto_now=True)
@ -42,3 +45,13 @@ class Character(BaseModel, DescriptionModel):
def text_color(self): def text_color(self):
return "black" if is_bright_color(self.color) else "white" return "black" if is_bright_color(self.color) else "white"
def larger_image(self):
if self.large_image:
return self.large_image
return self.token_image
def smaller_image(self):
if self.token_image:
return self.token_image
return self.larger_image

View file

@ -20,9 +20,9 @@
</div> </div>
<div class="col-8"> <div class="col-8">
<div class="character-heading" style="border-bottom-color: {{ character.color }}"> <div class="character-heading" style="border-bottom-color: {{ character.color }}">
{% if character.image %} {% if character.larger_image %}
{% thumbnail character.image "150x150" crop="center" as im %} {% thumbnail character.larger_image "150x150" crop="center" as im %}
<a href="{{ character.image.url }}" target="_blank"> <a href="{{ character.larger_image.url }}" target="_blank">
<img class="avatar avatar-image avatar-large rounded-circle" src="{{ im.url }}" width="{{ im.width }}" <img class="avatar avatar-image avatar-large rounded-circle" src="{{ im.url }}" width="{{ im.width }}"
height="{{ im.height }}" height="{{ im.height }}"
srcset="{{ im.url|srcset }}"> srcset="{{ im.url|srcset }}">

View file

@ -0,0 +1,76 @@
"""
based on https://github.com/akolpakov/django-unused-media/
licensed under MIT license
by Andrey Kolpakov
but using pathlib and considering django-tenants
"""
from pathlib import Path
from typing import Set
from django.apps import apps
from django.core.management import BaseCommand
from django.core.validators import EMPTY_VALUES
from django.db.models import FileField
from django_tenants.utils import tenant_context
from campaigns.models import Campaign
from rpg_notes.settings import MEDIA_ROOT
def get_file_fields():
all_models = apps.get_models()
fields = []
for model in all_models:
for field in model._meta.get_fields():
if isinstance(field, FileField):
fields.append(field)
return fields
def get_used_media() -> Set[Path]:
media = set()
for field in get_file_fields():
is_null = {
f'{field.name}__isnull': True,
}
is_empty = {
field.name: '',
}
storage = field.storage
for value in field.model._base_manager \
.values_list(field.name, flat=True) \
.exclude(**is_empty).exclude(**is_null):
if value not in EMPTY_VALUES:
media.add(Path(storage.path(value)).resolve())
return media
def get_all_media(schema: str) -> Set[Path]:
media = set()
media_root = Path(MEDIA_ROOT) / schema
cache_dir = media_root / "cache"
for file in media_root.glob("**/*"):
if not file.is_file():
continue
if cache_dir in file.parents:
continue
media.add(file.resolve())
return media
class Command(BaseCommand):
def handle(self, *args, **kwargs):
for campaign in Campaign.objects.exclude(pk=1):
print(campaign, campaign.schema_name)
with tenant_context(campaign):
used = get_used_media()
all = get_all_media(campaign.schema_name)
unused = all - used
print(all - used)

View file

@ -15,7 +15,8 @@
{% block content %} {% block content %}
<form method="post"> <form method="post"
{% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>
{% csrf_token %} {% csrf_token %}
<input type="submit" class="btn btn-primary" value="Update"> <input type="submit" class="btn btn-primary" value="Update">
{% bootstrap_form form %} {% bootstrap_form form %}

View file

@ -199,9 +199,8 @@ CSP_FRAME_ANCESTORS = ["'none'"]
CSP_INCLUDE_NONCE_IN = ['script-src'] CSP_INCLUDE_NONCE_IN = ['script-src']
THUMBNAIL_KVSTORE = "sorl.thumbnail.kvstores.redis_kvstore.KVStore" THUMBNAIL_KVSTORE = "sorl.thumbnail.kvstores.redis_kvstore.KVStore"
THUMBNAIL_REDIS_URL = "unix:///var/run/redis-rpgnotes/redis-server.sock?db=1"
redis_url = "redis://127.0.0.1:6379/9" if DEBUG else "unix:///var/run/redis-rpgnotes/redis-server.sock?db=2" redis_url = "redis://127.0.0.1:6379/9" if DEBUG else "unix:///var/run/redis-rpgnotes/redis-server.sock?db=2"
THUMBNAIL_REDIS_URL = redis_url.replace("?db=2", "?db=1")
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django_redis.cache.RedisCache", "BACKEND": "django_redis.cache.RedisCache",
@ -215,6 +214,8 @@ CACHES = {
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
SESSION_CACHE_ALIAS = "default" SESSION_CACHE_ALIAS = "default"
THUMBNAIL_DEBUG = DEBUG
if not DEBUG: if not DEBUG:
SECURE_SSL_REDIRECT = True SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 60 * 60 * 24 * 365 SECURE_HSTS_SECONDS = 60 * 60 * 24 * 365

View file

@ -9,20 +9,26 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto"> {% with url_name=request.resolver_match.url_name %}
<li class="nav-item"> <ul class="navbar-nav me-auto">
<a class="nav-link active" href="{% url "campaigndetail" %}">Home</a> <li class="nav-item">
</li> <a class="nav-link {% if url_name == 'campaigndetail' %}active{% endif %}"
<li class="nav-item"> href="{% url "campaigndetail" %}">Home</a>
<a class="nav-link" href="{% url "characterlist" %}">Characters</a> </li>
</li> <li class="nav-item">
<li class="nav-item"> <a class="nav-link {% if url_name == 'characterdetail' %}active{% endif %}"
<a class="nav-link" href="{% url "daylist" %}">Timeline</a> href="{% url "characterlist" %}">Characters</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url "lootlist" %}">Loot</a> <a class="nav-link {% if url_name == 'daydetail' %}active{% endif %}"
</li> href="{% url "daylist" %}">Timeline</a>
</ul> </li>
<li class="nav-item">
<a class="nav-link {% if url_name == 'lootlist' %}active{% endif %}"
href="{% url "lootlist" %}">Loot</a>
</li>
</ul>
{% endwith %}
<span class="navbar-text"> <span class="navbar-text">
{{ user }} {{ user }}
</span> </span>

16
utils/random_filename.py Normal file
View file

@ -0,0 +1,16 @@
import secrets
import string
from pathlib import Path
alphabet = string.ascii_letters + string.digits
def random_string(letters: int = 32) -> str:
return ''.join(secrets.choice(alphabet) for i in range(letters))
def get_file_path(instance, filename: str) -> str:
ext = Path(filename).suffix
rand = random_string()
letter = rand[0]
return f"{letter}/{rand}{ext}"