port to new django, AI automated
This commit is contained in:
@@ -1,41 +1,44 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models.fields.related import ForeignKey
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
############################# Helper Functions #######################################################
|
||||
|
||||
|
||||
def space_to_camelcase(value):
|
||||
"""Convert a space-separated string to camelCase."""
|
||||
|
||||
def camelcase():
|
||||
yield type(value).lower
|
||||
yield str.lower
|
||||
while True:
|
||||
yield type(value).capitalize
|
||||
yield str.capitalize
|
||||
|
||||
c = camelcase()
|
||||
return "".join(c.next()(x) if x else '_' for x in value.split())
|
||||
return "".join(next(c)(x) if x else "_" for x in value.split())
|
||||
|
||||
|
||||
def score_filename(score, original_name):
|
||||
fileExtension = os.path.splitext(original_name)[1]
|
||||
"""Generate filename for uploaded score files."""
|
||||
file_extension = os.path.splitext(original_name)[1]
|
||||
filename = "scores/"
|
||||
filename += space_to_camelcase(score.piece.title) + "/"
|
||||
filename += space_to_camelcase(score.score_type)
|
||||
filename += fileExtension
|
||||
filename += file_extension
|
||||
return filename
|
||||
|
||||
|
||||
def recordingFileName(recording, originalName):
|
||||
fileExtension = os.path.splitext(originalName)[1]
|
||||
def recording_filename(recording, original_name):
|
||||
"""Generate filename for uploaded recording files."""
|
||||
file_extension = os.path.splitext(original_name)[1]
|
||||
filename = "recordings/"
|
||||
filename += space_to_camelcase(recording.piece.title) + "/"
|
||||
filename += space_to_camelcase(recording.artist)
|
||||
filename += fileExtension
|
||||
filename += file_extension
|
||||
return filename
|
||||
|
||||
|
||||
@@ -43,130 +46,195 @@ def recordingFileName(recording, originalName):
|
||||
|
||||
|
||||
class Piece(models.Model):
|
||||
"""A musical piece in the repertoire."""
|
||||
|
||||
title = models.CharField(max_length=255, verbose_name=_("title"), unique=True)
|
||||
composer = models.CharField(max_length=255, blank=True, verbose_name=_("composer"))
|
||||
repertoire_nr = models.IntegerField(null=True, blank=True, unique=True, default=None)
|
||||
|
||||
def __unicode__(self):
|
||||
res = self.title
|
||||
return res
|
||||
|
||||
def isInRepertoire(self):
|
||||
return self.repertoire_nr is not None
|
||||
|
||||
def get_score_for_user(self, user):
|
||||
try:
|
||||
return ScoreUserMapping.objects.get(user=user, score__in=self.scores.all()).score
|
||||
except Piece.DoesNotExist:
|
||||
if len(self.scores.all()) > 0:
|
||||
return self.scores.all()[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def getRepertoire():
|
||||
return Piece.objects.filter(repertoire_nr__isnull=False).order_by('repertoire_nr')
|
||||
repertoire_nr = models.IntegerField(
|
||||
null=True, blank=True, unique=True, default=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("manage_scores", "Administrate and manage scores"),
|
||||
ordering = ["title"]
|
||||
permissions = (("manage_scores", "Administrate and manage scores"),)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def is_in_repertoire(self):
|
||||
return self.repertoire_nr is not None
|
||||
|
||||
# Backwards compatibility
|
||||
isInRepertoire = is_in_repertoire
|
||||
|
||||
def get_score_for_user(self, user):
|
||||
"""Get the preferred score for a user, or the first available score."""
|
||||
try:
|
||||
return ScoreUserMapping.objects.get(
|
||||
user=user, score__in=self.scores.all()
|
||||
).score
|
||||
except ScoreUserMapping.DoesNotExist:
|
||||
if self.scores.exists():
|
||||
return self.scores.first()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_repertoire():
|
||||
"""Get all pieces that are in the repertoire, ordered by repertoire number."""
|
||||
return Piece.objects.filter(repertoire_nr__isnull=False).order_by(
|
||||
"repertoire_nr"
|
||||
)
|
||||
|
||||
# Backwards compatibility
|
||||
getRepertoire = get_repertoire
|
||||
|
||||
|
||||
class BookLocation(models.Model):
|
||||
piece = models.ForeignKey('Piece', on_delete=models.PROTECT)
|
||||
"""Location of a piece in a physical book."""
|
||||
|
||||
piece = models.ForeignKey("Piece", on_delete=models.CASCADE)
|
||||
book = models.CharField(max_length=100, blank=False, verbose_name=_("Buch"))
|
||||
page = models.IntegerField(verbose_name=_("page"))
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s, %d" % (self.book, self.page)
|
||||
class Meta:
|
||||
ordering = ["book", "page"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.book}, {self.page}"
|
||||
|
||||
|
||||
class Score(models.Model):
|
||||
piece = ForeignKey('Piece', related_name="scores", on_delete=models.PROTECT)
|
||||
score_type = models.CharField(max_length=100, verbose_name="score type") # for example partitur, unterstimmen ...
|
||||
"""A score file for a piece (e.g., full score, parts, etc.)."""
|
||||
|
||||
piece = ForeignKey("Piece", on_delete=models.CASCADE, related_name="scores")
|
||||
score_type = models.CharField(max_length=100, verbose_name="score type")
|
||||
file = models.FileField(upload_to=score_filename, verbose_name=_("file"))
|
||||
uploaded_by = ForeignKey(User, verbose_name=_("uploaded_by"), on_delete=models.PROTECT)
|
||||
uploaded_by = ForeignKey(
|
||||
User, on_delete=models.SET_NULL, null=True, verbose_name=_("uploaded_by")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("piece", "score_type"),)
|
||||
ordering = ["score_type"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.piece.title} - {self.score_type}"
|
||||
|
||||
@property
|
||||
def image_file_name(self):
|
||||
"""Get the path for the cached image preview of this score."""
|
||||
return os.path.splitext("image_cache/" + str(self.file))[0] + ".jpg"
|
||||
|
||||
@staticmethod
|
||||
def pdf2jpg(source_file, target_file, resolution=100, crop=15):
|
||||
from wand.image import Image
|
||||
ret = True
|
||||
"""Convert a PDF to a JPG preview image."""
|
||||
try:
|
||||
with Image(filename=source_file, resolution=(resolution, resolution)) as img:
|
||||
img.crop(crop, crop, width=img.width - 2 * crop, height=int(0.5 * img.height) - 2 * crop)
|
||||
img.format = 'jpeg'
|
||||
img.save(filename=target_file)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
ret = False
|
||||
from wand.image import Image
|
||||
|
||||
return ret
|
||||
with Image(
|
||||
filename=source_file, resolution=(resolution, resolution)
|
||||
) as img:
|
||||
img.crop(
|
||||
crop,
|
||||
crop,
|
||||
width=img.width - 2 * crop,
|
||||
height=int(0.5 * img.height) - 2 * crop,
|
||||
)
|
||||
img.format = "jpeg"
|
||||
img.save(filename=target_file)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error converting PDF to JPG: {e}")
|
||||
return False
|
||||
|
||||
def get_image_file(self):
|
||||
"""Get or create a preview image for this score."""
|
||||
from django.conf import settings
|
||||
|
||||
inputFile = settings.MEDIA_ROOT + "/" + str(self.file)
|
||||
cacheFile = settings.MEDIA_ROOT + "/" + str(self.image_file_name)
|
||||
input_file = settings.MEDIA_ROOT + "/" + str(self.file)
|
||||
cache_file = settings.MEDIA_ROOT + "/" + str(self.image_file_name)
|
||||
|
||||
# Create a jpg for this score, if it does not exist yet
|
||||
if not os.path.exists(cacheFile):
|
||||
if not os.path.exists(os.path.dirname(cacheFile)):
|
||||
os.makedirs(os.path.dirname(cacheFile))
|
||||
Score.pdf2jpg(inputFile, cacheFile)
|
||||
if not os.path.exists(cache_file):
|
||||
cache_dir = os.path.dirname(cache_file)
|
||||
if not os.path.exists(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
Score.pdf2jpg(input_file, cache_file)
|
||||
|
||||
return self.image_file_name
|
||||
|
||||
def is_active_score(self, user):
|
||||
return len(ScoreUserMapping.objects.filter(score=self, user=user)) > 0
|
||||
|
||||
class Meta:
|
||||
unique_together = (("piece", "score_type"),)
|
||||
ordering = ['score_type']
|
||||
"""Check if this is the active score for a user."""
|
||||
return ScoreUserMapping.objects.filter(score=self, user=user).exists()
|
||||
|
||||
|
||||
class Recording(models.Model):
|
||||
piece = ForeignKey('Piece', related_name='recordings', on_delete=models.PROTECT)
|
||||
"""An audio recording of a piece."""
|
||||
|
||||
piece = ForeignKey("Piece", on_delete=models.CASCADE, related_name="recordings")
|
||||
artist = models.CharField(max_length=100, verbose_name=_("Artist"))
|
||||
file = models.FileField(upload_to=recordingFileName, verbose_name=_("file"))
|
||||
uploaded_by = ForeignKey(User, verbose_name=_("uploaded_by"), on_delete=models.PROTECT)
|
||||
file = models.FileField(upload_to=recording_filename, verbose_name=_("file"))
|
||||
uploaded_by = ForeignKey(
|
||||
User, on_delete=models.SET_NULL, null=True, verbose_name=_("uploaded_by")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("piece", "artist"),)
|
||||
ordering = ['artist']
|
||||
ordering = ["artist"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.piece.title} - {self.artist}"
|
||||
|
||||
|
||||
class YoutubeRecording(models.Model):
|
||||
piece = models.ForeignKey('Piece', related_name="youtubeLinks", on_delete=models.PROTECT)
|
||||
link = models.CharField(max_length=300, blank=False)
|
||||
uploaded_by = ForeignKey(User, verbose_name=_("uploaded_by"), on_delete=models.PROTECT)
|
||||
"""A YouTube link for a piece."""
|
||||
|
||||
youtubeRegex = re.compile(u'(?:https://)?(?:http://)?www.youtube.(?:com|de)/watch\?v=(?P<videoID>[-\w]*)')
|
||||
piece = models.ForeignKey(
|
||||
"Piece", on_delete=models.CASCADE, related_name="youtubeLinks"
|
||||
)
|
||||
link = models.CharField(max_length=300, blank=False)
|
||||
uploaded_by = ForeignKey(
|
||||
User, on_delete=models.SET_NULL, null=True, verbose_name=_("uploaded_by")
|
||||
)
|
||||
|
||||
youtube_regex = re.compile(
|
||||
r"(?:https://)?(?:http://)?www\.youtube\.(?:com|de)/watch\?v=(?P<videoID>[-\w]*)"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("link", "piece"),)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.piece.title} - YouTube"
|
||||
|
||||
@property
|
||||
def embed_html(self):
|
||||
"""Generate embeddable HTML for this YouTube video."""
|
||||
replacement = """
|
||||
<div class="embed-container"><iframe src="//www.youtube.de/embed/\g<videoID>" frameborder="0" allowfullscreen></iframe> </div>
|
||||
<div class="embed-container">
|
||||
<iframe src="//www.youtube.com/embed/\\g<videoID>"
|
||||
frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
"""
|
||||
return mark_safe(YoutubeRecording.youtubeRegex.sub(replacement, self.link))
|
||||
|
||||
class Meta:
|
||||
unique_together = ("link", "piece")
|
||||
return mark_safe(self.youtube_regex.sub(replacement, self.link))
|
||||
|
||||
|
||||
class ScoreUserMapping(models.Model):
|
||||
score = ForeignKey('Score', on_delete=models.PROTECT)
|
||||
user = models.OneToOneField(User, on_delete=models.PROTECT)
|
||||
piece = ForeignKey('Piece', on_delete=models.PROTECT)
|
||||
"""Maps a user's preferred score for each piece."""
|
||||
|
||||
score = ForeignKey("Score", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
piece = ForeignKey("Piece", on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("piece", "user"),)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username} -> {self.score}"
|
||||
|
||||
@staticmethod
|
||||
def add_user_score_mapping(score, user):
|
||||
"""Set the preferred score for a user for a given piece."""
|
||||
piece = score.piece
|
||||
ScoreUserMapping.objects.filter(user=user, piece=piece).delete()
|
||||
ScoreUserMapping.objects.create(score=score, user=user, piece=piece)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("piece", "user")
|
||||
|
||||
Reference in New Issue
Block a user