class Series(models.Model): name = models.CharField(max_length=200) slug = AutoSlugField(populate_from='name', editable=True, blank=True, overwrite=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False) description = extra.MarkdownTextField(blank=True, null=True) class Meta: app_label = 'alibrary' verbose_name = _('Series') verbose_name_plural = _('Series') ordering = ('-name', ) def __unicode__(self): return '%s' % (self.name)
class Profile(TimestampedModelMixin, UUIDModelMixin, MigrationMixin): GENDER_CHOICES = ( (0, _('Male')), (1, _('Female')), (2, _('Other')), ) user = models.OneToOneField(settings.AUTH_USER_MODEL, unique=True, on_delete=models.CASCADE) mentor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="godchildren") #Personal gender = models.PositiveSmallIntegerField(_('gender'), choices=GENDER_CHOICES, blank=True, null=True) birth_date = models.DateField(_('Date of birth'), blank=True, null=True, help_text=_('Format: YYYY-MM-DD')) pseudonym = models.CharField( blank=True, null=True, max_length=250, help_text=_('Will appear instead of your first- & last name')) description = models.CharField(_('Disambiguation'), blank=True, null=True, max_length=250) biography = extra.MarkdownTextField(blank=True, null=True) image = models.ImageField(verbose_name=_('Profile Image'), upload_to=filename_by_uuid, null=True, blank=True) # Contact (personal) mobile = PhoneNumberField(_('mobile'), blank=True, null=True) phone = PhoneNumberField(_('phone'), blank=True, null=True) fax = PhoneNumberField(_('fax'), blank=True, null=True) address1 = models.CharField(_('address'), null=True, blank=True, max_length=100) address2 = models.CharField(_('address (secondary)'), null=True, blank=True, max_length=100) city = models.CharField(_('city'), null=True, blank=True, max_length=100) zip = models.CharField(_('zip'), null=True, blank=True, max_length=10) country = models.ForeignKey(Country, blank=True, null=True) iban = models.CharField(_('IBAN'), null=True, blank=True, max_length=120) paypal = models.EmailField(_('Paypal'), null=True, blank=True, max_length=200) # relations expertise = models.ManyToManyField('Expertise', verbose_name=_('Fields of expertise'), blank=True) # tagging (d_tags = "display tags") d_tags = tagging.fields.TagField(max_length=1024, verbose_name="Tags", blank=True, null=True) # alpha features enable_alpha_features = models.BooleanField(default=False) class Meta: app_label = 'profiles' verbose_name = _('user profile') verbose_name_plural = _('user profiles') db_table = 'user_profiles' ordering = ('-user__last_login', ) permissions = ( ('mentor_profiles', _('Mentoring profiles')), ('view_profiles_private', _('View private profile-data.')), ) def __unicode__(self): return u"%s" % self.get_display_name() def get_full_name(self): if self.user: return self.user.get_full_name() @property def name(self): return self.get_display_name() @property def main_image(self): return self.image def get_display_name(self): if self.pseudonym: return self.pseudonym if self.user.get_full_name(): return self.user.get_full_name() return self.user.username @property def is_approved(self): if self.user in Group.objects.get(name='Mentor').user_set.all(): return True return def approve(self, mentor, level): groups_to_add = [] if level == 'music_pro': groups_to_add = ( 'Music PRO', 'Mentor', ) if level == 'radio_pro': groups_to_add = ( 'Radio PRO', 'Mentor', ) groups = Group.objects.filter(name__in=groups_to_add) for group in groups: self.user.groups.add(group) self.user.groups.remove(Group.objects.get(name=DEFAULT_GROUP)) @property def age(self): if self.birth_date: return u"%s" % relativedelta.relativedelta(datetime.date.today(), self.birth_date).years else: return None def get_ct(self): return '{}.{}'.format(self._meta.app_label, self.__class__.__name__).lower() def get_absolute_url(self): # return reverse('profiles-profile-detail-legacy', kwargs={ 'username': self.user.username }) return reverse('profiles-profile-detail', kwargs={'uuid': str(self.uuid)}) @models.permalink def get_edit_url(self): return ('profiles-profile-edit', ) def get_admin_url(self): return reverse("admin:profiles_profile_change", args=(self.pk, )) def get_api_url(self): return None # return reverse('api_dispatch_detail', kwargs={ # 'api_name': 'v1', # 'resource_name': 'profile', # 'pk': self.pk # }) def get_groups(self): return self.user.groups def save(self, *args, **kwargs): super(Profile, self).save(*args, **kwargs)
class Community(UUIDModelMixin, MigrationMixin): name = models.CharField(max_length=200, db_index=True) slug = AutoSlugField(populate_from='name', editable=True, blank=True, overwrite=True) group = models.OneToOneField(Group, unique=True, null=True, blank=True) members = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True) # auto-update created = models.DateTimeField(auto_now_add=True, editable=False) updated = models.DateTimeField(auto_now=True, editable=False) # Profile description = extra.MarkdownTextField(blank=True, null=True) image = models.ImageField(verbose_name=_('Profile Image'), upload_to=filename_by_uuid, null=True, blank=True) # Contact mobile = PhoneNumberField(_('mobile'), blank=True, null=True) phone = PhoneNumberField(_('phone'), blank=True, null=True) fax = PhoneNumberField(_('fax'), blank=True, null=True) email = models.EmailField(blank=True, null=True) address1 = models.CharField(_('address'), null=True, blank=True, max_length=100) address2 = models.CharField(_('address (secondary)'), null=True, blank=True, max_length=100) city = models.CharField(_('city'), null=True, blank=True, max_length=100) zip = models.CharField(_('zip'), null=True, blank=True, max_length=10) #country = CountryField(blank=True, null=True) country = models.ForeignKey(Country, blank=True, null=True) # relations expertise = models.ManyToManyField('Expertise', verbose_name=_('Fields of expertise'), blank=True) # tagging (d_tags = "display tags") d_tags = tagging.fields.TagField(verbose_name="Tags", blank=True, null=True) class Meta: app_label = 'profiles' verbose_name = _('Community') verbose_name_plural = _('Communities') """ permissions = ( ('mentor_profiles', 'Mentoring profiles'), ) """ def __unicode__(self): return u"%s" % self.name def save(self, *args, **kwargs): t_tags = '' """""" for tag in self.tags: t_tags += '%s, ' % tag self.tags = t_tags self.d_tags = t_tags[:245] super(Community, self).save(*args, **kwargs)
class Distributor(MigrationMixin, UUIDModelMixin, TimestampedModelMixin, models.Model): """ TODO: suggest to remove 'distributor' implementation. it is not used at the moment (90 entries, last 2014...) """ TYPE_CHOICES = ( ('unknown', _('Unknown')), ('major', _('Major')), ('indy', _('Independent')), ('other', _('Other')), ) type = models.CharField(verbose_name="Distributor type", max_length=12, default='unknown', choices=TYPE_CHOICES) slug = AutoSlugField(populate_from='name', editable=True, blank=True, overwrite=True) name = models.CharField(max_length=400) description = extra.MarkdownTextField(blank=True, null=True) code = models.CharField(max_length=50) address = models.TextField(blank=True, null=True) email = models.EmailField(blank=True, null=True) phone = PhoneNumberField(blank=True, null=True) fax = PhoneNumberField(blank=True, null=True) country = models.ForeignKey(Country, blank=True, null=True) # relations parent = models.ForeignKey('self', null=True, blank=True, related_name='children') labels = models.ManyToManyField('Label', through='DistributorLabel', blank=True, related_name="distributors") relations = GenericRelation('Relation') d_tags = tagging.fields.TagField(verbose_name="Tags", max_length=1024, blank=True, null=True) objects = models.Manager() class Meta: app_label = 'alibrary' verbose_name = _('Distributor') verbose_name_plural = _('Distributors') ordering = ('name', ) def __unicode__(self): return self.name @models.permalink def get_absolute_url(self): return ('alibrary-distributor-detail', [self.slug]) @models.permalink def get_edit_url(self): return ('alibrary-distributor-edit', [self.pk]) def save(self, *args, **kwargs): unique_slugify(self, self.name) t_tags = '' for tag in self.tags: t_tags += '%s, ' % tag self.tags = t_tags self.d_tags = t_tags super(Distributor, self).save(*args, **kwargs)
class Playlist(MigrationMixin, TimestampedModelMixin, models.Model): TYPE_BASKET = 'basket' TYPE_PLAYLIST = 'playlist' TYPE_BROADCAST = 'broadcast' TYPE_OTHER = 'other' TYPE_CHOICES = ( (TYPE_BASKET, _('Private Playlist')), (TYPE_PLAYLIST, _('Public Playlist')), (TYPE_BROADCAST, _('Broadcast')), (TYPE_OTHER, _('Other')), ) name = models.CharField(max_length=200) slug = AutoSlugField(populate_from='name', editable=True, blank=True, overwrite=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False) status = models.PositiveIntegerField( default=0, choices=alibrary_settings.PLAYLIST_STATUS_CHOICES) type = models.CharField(max_length=12, default='basket', null=True, choices=TYPE_CHOICES) broadcast_status = models.PositiveIntegerField( default=0, choices=alibrary_settings.PLAYLIST_BROADCAST_STATUS_CHOICES) broadcast_status_messages = JSONField(blank=True, null=True, default=None) playout_mode_random = models.BooleanField( verbose_name=_('Shuffle Playlist'), default=False, help_text=_( 'If enabled the order of the tracks will be randomized for playout' )) rotation = models.BooleanField(default=True) rotation_date_start = models.DateField(verbose_name=_('Rotate from'), blank=True, null=True) rotation_date_end = models.DateField(verbose_name=_('Rotate until'), blank=True, null=True) main_image = models.ImageField(verbose_name=_('Image'), upload_to=upload_image_to, storage=OverwriteStorage(), null=True, blank=True) # relations user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None, related_name='playlists') items = models.ManyToManyField('PlaylistItem', through='PlaylistItemPlaylist', blank=True) # tagging (d_tags = "display tags") d_tags = tagging.fields.TagField(max_length=1024, verbose_name='Tags', blank=True, null=True) # updated/calculated on save duration = models.IntegerField(null=True, default=0) target_duration = models.PositiveIntegerField( default=0, null=True, choices=alibrary_settings.PLAYLIST_TARGET_DURATION_CHOICES) dayparts = models.ManyToManyField(Daypart, blank=True, related_name='daypart_plalists') seasons = models.ManyToManyField('Season', blank=True, related_name='season_plalists') weather = models.ManyToManyField('Weather', blank=True, related_name='weather_plalists') # series series = models.ForeignKey(Series, null=True, blank=True, on_delete=models.SET_NULL) series_number = models.PositiveIntegerField(null=True, blank=True) # is currently selected as default? is_current = models.BooleanField(_('Currently selected?'), default=False) description = extra.MarkdownTextField(blank=True, null=True) mixdown_file = models.FileField(null=True, blank=True, upload_to=upload_mixdown_to) emissions = GenericRelation('abcast.Emission') # meta class Meta: app_label = 'alibrary' verbose_name = _('Playlist') verbose_name_plural = _('Playlists') ordering = ('-updated', ) permissions = ( ('view_playlist', 'View Playlist'), ('edit_playlist', 'Edit Playlist'), ('schedule_playlist', 'Schedule Playlist'), ('admin_playlist', 'Edit Playlist (extended)'), ) def __unicode__(self): return self.name def get_ct(self): return '{}.{}'.format(self._meta.app_label, self.__class__.__name__).lower() def get_absolute_url(self): return reverse('alibrary-playlist-detail', kwargs={ 'slug': self.slug, }) def get_edit_url(self): return reverse("alibrary-playlist-edit", args=(self.pk, )) def get_delete_url(self): return reverse("alibrary-playlist-delete", args=(self.pk, )) def get_admin_url(self): return reverse("admin:alibrary_playlist_change", args=(self.pk, )) def get_duration(self): duration = 0 try: for item in self.items.all(): duration += item.content_object.get_duration() pip = PlaylistItemPlaylist.objects.get(playlist=self, item=item) duration -= pip.cue_in duration -= pip.cue_out duration -= pip.fade_cross except: pass return duration # TODO: remove usages and use generic reverse 'emissions' instead def get_emissions(self): from abcast.models import Emission ctype = ContentType.objects.get_for_model(self) emissions = Emission.objects.filter( content_type__pk=ctype.id, object_id=self.id).order_by('-time_start') return emissions def get_api_url(self): return reverse('api_dispatch_detail', kwargs={ 'api_name': 'v1', 'resource_name': 'library/playlist', 'pk': self.pk }) + '' def get_api_simple_url(self): return reverse('api_dispatch_detail', kwargs={ 'api_name': 'v1', 'resource_name': 'library/simpleplaylist', 'pk': self.pk }) + '' def can_be_deleted(self): can_delete = False reason = _('This playlist cannot be deleted.') if self.type == 'basket': can_delete = True reason = None if self.type == 'playlist': can_delete = False reason = _( 'Playlist "%s" is public. It cannot be deleted anymore.' % self.name) if self.type == 'broadcast': can_delete = False reason = _( 'Playlist "%s" published for broadcast. It cannot be deleted anymore.' % self.name) return can_delete, reason def get_transform_status(self, target_type): """ check if transformation is possible / what needs to be done Not so nicely here - but... """ status = False """ criterias = [ { 'key': 'tags', 'name': _('Tags'), 'status': True, 'warning': _('Please add some tags'), }, { 'key': 'description', 'name': _('Description'), 'status': False, 'warning': _('Please add a description'), } ] """ criterias = [] # "basket" only used while dev... if target_type == 'basket': status = True if target_type == 'playlist': status = True # tags tag_count = self.tags.count() if tag_count < 1: status = False criteria = { 'key': 'tags', 'name': _('Tags'), 'status': tag_count > 0, 'warning': _('Please add some tags'), } criterias.append(criteria) # scheduled if self.type == 'broadcast': schedule_count = self.get_emissions().count() if schedule_count > 0: status = False criteria = { 'key': 'scheduled', 'name': _('Playlist already scheduled') if schedule_count > 0 else _('Playlist not scheduled'), 'status': schedule_count < 1, 'warning': _('This playlist has already ben scheduled %s times. Remove all scheduler entries to "un-broadcast" this playlist.' % schedule_count), } if schedule_count > 0: criterias.append(criteria) if target_type == 'broadcast': status = True # tags tag_count = self.tags.count() if tag_count < 1: status = False criteria = { 'key': 'tags', 'name': _('Tags'), 'status': tag_count > 0, 'warning': _('Please add some tags'), } criterias.append(criteria) # dayparts dp_count = self.dayparts.count() if not dp_count: status = False criteria = { 'key': 'dayparts', 'name': _('Dayparts'), 'status': dp_count > 0, 'warning': _('Please specify the dayparts'), } criterias.append(criteria) # duration if not self.broadcast_status == 1: status = False criteria = { 'key': 'duration', 'name': _('Duration'), 'status': True if self.broadcast_status == 1 else False, 'warning': _('Durations do not match'), # 'warning': ', '.join(self.broadcast_status_messages), } criterias.append(criteria) transformation = {'criterias': criterias, 'status': status} return transformation ################################################################### # legacy version - used in tastypie API (v1) ################################################################### def add_items_by_ids(self, ids, ct, timing=None): from alibrary.models.mediamodels import Media log.debug('add media to playlist: {}'.format(', '.join(ids))) for id in ids: id = int(id) co = None if ct == 'media': co = Media.objects.get(pk=id) if co: i = PlaylistItem(content_object=co) i.save() """ ctype = ContentType.objects.get_for_model(co) item, created = PlaylistItem.objects.get_or_create(object_id=co.pk, content_type=ctype) """ pi, created = PlaylistItemPlaylist.objects.get_or_create( item=i, playlist=self, position=self.items.count()) if timing: try: pi.fade_in = timing['fade_in'] pi.fade_out = timing['fade_out'] pi.cue_in = timing['cue_in'] pi.cue_out = timing['cue_out'] pi.save() except: pass self.save() ################################################################### # new version - used in DRF API (v245) ################################################################### def add_item(self, item, cue_and_fade=None, commit=True): log.debug('add item to playlist: {}'.format(item)) playlist_item = PlaylistItem(content_object=item) playlist_item.save() playlist_item_playlist = PlaylistItemPlaylist( item=playlist_item, playlist=self, position=self.items.count()) if cue_and_fade: playlist_item_playlist.fade_in = cue_and_fade['fade_in'] playlist_item_playlist.fade_out = cue_and_fade['fade_out'] playlist_item_playlist.cue_in = cue_and_fade['cue_in'] playlist_item_playlist.cue_out = cue_and_fade['cue_out'] playlist_item_playlist.save() if commit: self.save() def reorder_items_by_uuids(self, uuids): i = 0 for uuid in uuids: pi = PlaylistItemPlaylist.objects.get(uuid=uuid) pi.position = i pi.save() i += 1 self.save() def convert_to(self, playlist_type): log.debug('requested to convert "%s" from %s to %s' % (self.name, self.type, playlist_type)) if playlist_type == 'broadcast': self.broadcast_status, self.broadcast_status_messages = self.self_check( ) transformation = self.get_transform_status(playlist_type) status = transformation['status'] if playlist_type == 'broadcast' and status: _status, messages = self.self_check() if _status == 1: status = True if status: self.type = playlist_type self.created = timezone.now() self.save() return self, status def get_items(self): pis = PlaylistItemPlaylist.objects.filter( playlist=self).order_by('position') items = [] for pi in pis: item = pi.item item.cue_in = pi.cue_in item.cue_out = pi.cue_out item.fade_in = pi.fade_in item.fade_out = pi.fade_out item.fade_cross = pi.fade_cross # get the actual playout duration try: # print '// getting duration for:' # print '%s - %s' % (item.content_object.pk, item.content_object.name) # print 'obj duration: %s' % item.content_object.duration_s item.playout_duration = item.content_object.duration_ms - item.cue_in - item.cue_out - item.fade_cross except Exception as e: log.warning('unable to get duration: {}'.format(e)) item.playout_duration = 0 items.append(item) return items def self_check(self): """ check if everything is fine to be 'scheduled' """ log.info('Self check requested for: %s' % self.name) status = 1 # set to 'OK' messages = [] try: # check ready-status of related media for item in self.items.all(): # log.debug('Self check content object: %s' % item.content_object) # log.debug('Self check master: %s' % item.content_object.master) # log.debug('Self check path: %s' % item.content_object.master.path) # check if file available try: with open(item.content_object.master.path): pass except IOError as e: log.warning( _('File does not exists: %s | %s') % (e, item.content_object.master.path)) status = 99 messages.append( _('File does not exists: %s | %s') % (e, item.content_object.master.path)) """ pip = PlaylistItemPlaylist.objects.get(playlist=self, item=item) duration -= pip.cue_in duration -= pip.cue_out duration -= pip.fade_cross """ # check duration & matching target_duration """ compare durations. target: in seconds | calculated duration in milliseconds """ diff = self.get_duration() - self.target_duration * 1000 if abs(diff) > DURATION_MAX_DIFF: messages.append( _('durations do not match. difference is: %s seconds' % int(diff / 1000))) log.warning( 'durations do not match. difference is: %s seconds' % int(diff / 1000)) status = 2 except Exception as e: messages.append(_('Validation error: %s ' % e)) log.warning('validation error: %s ' % e) status = 99 if status == 1: log.info('Playlist "%s" checked - all fine!' % (self.name)) return status, messages ################################################################### # playlist mixdown ################################################################### def get_mixdown(self): """ get mixdown from api """ return MixdownAPIClient().get_for_playlist(self) def request_mixdown(self): """ request (re-)creation of mixdown """ return MixdownAPIClient().request_for_playlist(self) def download_mixdown(self): """ download generated mixdown from api & store locally (in `mixdown_file` field) """ if not self.mixdown: log.info('mixdown not available on api') return if not self.mixdown['status'] == 3: log.info('mixdown not ready on api') return url = self.mixdown['mixdown_file'] log.debug('download mixdown from api: {} > {}'.format(url, self.name)) f_temp = NamedTemporaryFile(delete=True) f_temp.write(urlopen(url).read()) f_temp.flush() # wipe existing file try: self.mixdown_file.delete(False) except IOError: pass self.mixdown_file.save(url.split('/')[-1], File(f_temp)) return MixdownAPIClient().request_for_playlist(self) @property def sorted_items(self): return self.items.order_by('playlist_items__position') @cached_property def num_media(self): return self.items.count() @cached_property def mixdown(self): return self.get_mixdown() # provide type-based properties @property def is_broadcast(self): return self.type == Playlist.TYPE_BROADCAST @property def is_playlist(self): return self.type == Playlist.TYPE_PLAYLIST @cached_property def is_archived(self): if not self.type == Playlist.TYPE_BROADCAST: return if self.rotation_date_end and self.rotation_date_end < timezone.now( ).date(): return True @cached_property def is_upcoming(self): if not self.type == Playlist.TYPE_BROADCAST: return if self.rotation_date_start and self.rotation_date_start > timezone.now( ).date(): return True @cached_property def series_display(self): if not self.series: return if self.series_number: return '{} #{}'.format(self.series.name, self.series_number) return self.series.name @cached_property def last_emission(self): ############################################################### # we cannot filter a prefetched qs - so to avoid # additional queries we have to loop the qs and 'filter' # 'manually' ############################################################### for emission in self.emissions.order_by('-time_start'): if emission.time_start < timezone.now(): return emission @cached_property def next_emission(self): ############################################################### # we cannot filter a prefetched qs - so to avoid # additional queries we have to loop the qs and 'filter' # 'manually' ############################################################### for emission in self.emissions.order_by('time_start'): if emission.time_start > timezone.now(): return emission def save(self, *args, **kwargs): # status update if self.status == 0: self.status = 2 duration = 0 try: duration = self.get_duration() except Exception as e: pass self.duration = duration # TODO: maybe move self.broadcast_status, self.broadcast_status_messages = self.self_check( ) if self.broadcast_status == 1: self.status = 1 # 'ready' else: self.status = 99 # 'error' super(Playlist, self).save(*args, **kwargs)
class Release(MigrationMixin, UUIDModelMixin, TimestampedModelMixin, models.Model): # core fields name = models.CharField(max_length=200, db_index=True) slug = AutoSlugField(populate_from='name', editable=True, blank=True, overwrite=True) license = models.ForeignKey(License, blank=True, null=True, related_name='release_license') release_country = models.ForeignKey(Country, blank=True, null=True) main_image = models.ImageField(verbose_name=_('Cover'), upload_to=upload_cover_to, storage=OverwriteStorage(), null=True, blank=True) catalognumber = models.CharField(max_length=50, blank=True, null=True) """ releasedate stores the 'real' time, approx is for inputs lik 2012-12 etc. """ releasedate = models.DateField(blank=True, null=True) releasedate_approx = ApproximateDateField(verbose_name="Releasedate", blank=True, null=True) pressings = models.PositiveIntegerField(default=0) TOTALTRACKS_CHOICES = ((x, x) for x in range(1, 301)) totaltracks = models.IntegerField(verbose_name=_('Total Tracks'), blank=True, null=True, choices=TOTALTRACKS_CHOICES) asin = models.CharField(max_length=150, blank=True) RELEASESTATUS_CHOICES = ( (None, _('Not set')), ('official', _('Official')), ('promo', _('Promo')), ('bootleg', _('Bootleg')), ('other', _('Other')), ) releasestatus = models.CharField(max_length=60, blank=True, choices=RELEASESTATUS_CHOICES) excerpt = models.TextField(blank=True, null=True) description = extra.MarkdownTextField(blank=True, null=True) releasetype = models.CharField( verbose_name="Release type", max_length=24, blank=True, null=True, choices=alibrary_settings.RELEASETYPE_CHOICES) label = models.ForeignKey('alibrary.Label', blank=True, null=True, related_name='release_label', on_delete=models.SET_NULL) media = models.ManyToManyField('alibrary.Media', through='ReleaseMedia', blank=True, related_name="releases") owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="releases_owner", on_delete=models.SET_NULL) creator = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="releases_creator", on_delete=models.SET_NULL) last_editor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="releases_last_editor", on_delete=models.SET_NULL) publisher = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name="releases_publisher", on_delete=models.SET_NULL) barcode = models.CharField(max_length=32, blank=True, null=True) extra_artists = models.ManyToManyField('alibrary.Artist', through='ReleaseExtraartists', blank=True) album_artists = models.ManyToManyField('alibrary.Artist', through='ReleaseAlbumartists', related_name="release_albumartists", blank=True) relations = GenericRelation(Relation) d_tags = tagging.fields.TagField(max_length=1024, verbose_name="Tags", blank=True, null=True) objects = ReleaseManager() class Meta: app_label = 'alibrary' verbose_name = _('Release') verbose_name_plural = _('Releases') ordering = ('-created', ) permissions = ( ('view_release', 'View Release'), ('edit_release', 'Edit Release'), ('merge_release', 'Merge Releases'), ('admin_release', 'Edit Release (extended)'), ) def __unicode__(self): return self.name @property def classname(self): return self.__class__.__name__ @property def publish_date(self): # compatibility hack TODO: refactor all dependencies return datetime.utcnow() def is_active(self): now = date.today() try: if not self.releasedate: return True if self.releasedate <= now: return True except: pass return False @property def is_promotional(self): # TODO: refactor query to reduce db hits if self.releasedate: if self.releasedate > datetime.now().date(): return True if License.objects.filter(media_license__in=self.get_media(), is_promotional=True).distinct().exists(): return True return False @property def is_new(self): if self.is_promotional: return False if self.releasedate and self.releasedate >= ( datetime.now() - timedelta(days=14)).date(): return True return False def get_lookup_providers(self): providers = [] for key, name in LOOKUP_PROVIDERS: relations = self.relations.filter(service=key) relation = None if relations.count() == 1: relation = relations[0] providers.append({'key': key, 'name': name, 'relation': relation}) return providers def get_ct(self): return '{}.{}'.format(self._meta.app_label, self.__class__.__name__).lower() def get_absolute_url(self): try: return reverse('alibrary-release-detail', kwargs={ 'pk': self.pk, 'slug': self.slug, }) except NoReverseMatch: translation.activate('en') return reverse('alibrary-release-detail', kwargs={ 'pk': self.pk, 'slug': self.slug, }) def get_edit_url(self): return reverse("alibrary-release-edit", args=(self.pk, )) def get_admin_url(self): return reverse("admin:alibrary_release_change", args=(self.pk, )) def get_api_url(self): return reverse('api_dispatch_detail', kwargs={ 'api_name': 'v1', 'resource_name': 'library/release', 'pk': self.pk }) + '' def get_api_simple_url(self): return reverse('api_dispatch_detail', kwargs={ 'api_name': 'v1', 'resource_name': 'library/simplerelease', 'pk': self.pk }) + '' def get_media(self): from alibrary.models import Media return Media.objects.filter(release=self).select_related( 'artist', 'preflight_check') def get_products(self): return self.releaseproduct.all() def get_media_indicator(self): media = self.get_media() indicator = [] if self.totaltracks: for i in range(self.totaltracks): indicator.append(0) for m in media: try: indicator[m.tracknumber - 1] = 3 except Exception as e: pass else: for m in media: indicator.append(2) return indicator def get_license(self): licenses = License.objects.filter( media_license__in=self.get_media()).distinct() if not licenses.exists(): return {'name': _(u'Not Defined')} if licenses.count() > 1: license, created = License.objects.get_or_create(name="Multiple") return license if licenses.count() == 1: return licenses[0] """ compose artist display as string """ def get_artist_display(self): artist_str = '' artists = self.get_artists() if len(artists) > 1: try: for artist in artists: if artist['join_phrase']: artist_str += ' %s ' % artist['join_phrase'] artist_str += artist['artist'].name except: artist_str = artists[0]['artist'].name else: try: artist_str = artists[0]['artist'].name except: try: artist_str = artists[0].name except: artist_str = _('Unknown Artist') return artist_str def get_artists(self): artists = [] if self.album_artists.count() > 0: for albumartist in self.release_albumartist_release.all(): artists.append({ 'artist': albumartist.artist, 'join_phrase': albumartist.join_phrase }) return artists medias = self.get_media() for media in medias: artists.append(media.artist) artists = list(set(artists)) if len(artists) > 1: from alibrary.models import Artist a, c = Artist.objects.get_or_create(name="Various Artists") artists = [a] return artists def get_extra_artists(self): artists = [] roles = ReleaseExtraartists.objects.filter(release=self.pk) for role in roles: try: role.artist.profession = role.profession.name artists.append(role.artist) except: pass return artists def get_downloads(self): return None def get_download_url(self, format, version): return '%sdownload/%s/%s/' % (self.get_absolute_url(), format, version) def get_cache_file_path(self, format, version): tmp_directory = TEMP_DIR file_name = '%s_%s_%s.%s' % (format, version, str(self.uuid), 'zip') tmp_path = '%s/%s' % (tmp_directory, file_name) return tmp_path def clear_cache_file(self): tmp_directory = TEMP_DIR pattern = '*%s.zip' % (str(self.uuid)) versions = glob.glob('%s/%s' % (tmp_directory, pattern)) try: for version in versions: os.remove(version) except Exception as e: pass def get_cache_file(self, format, version): cache_file_path = self.get_cache_file_path(format, version) if os.path.isfile(cache_file_path): logger.info('serving from cache: %s' % (cache_file_path)) return cache_file_path else: return self.build_cache_file(format, version) def build_cache_file(self, format, version): cache_file_path = self.get_cache_file_path(format, version) logger.info('building cache for: %s' % (cache_file_path)) try: os.remove(cache_file_path) except Exception as e: pass archive_file = ZipFile(cache_file_path, "w") """ adding corresponding media files """ for media in self.get_media(): media_cache_file = media.inject_metadata(format, version) # filename for the file archive file_name = '%02d - %s - %s' % (media.tracknumber, media.artist.name, media.name) file_name = '%s.%s' % (file_name.encode('ascii', 'ignore'), format) archive_file.write(media_cache_file.path, file_name) return cache_file_path def get_extraimages(self): return None # OBSOLETE def complete_by_mb_id(self, mb_id): obj = self log = logging.getLogger('alibrary.release.complete_by_mb_id') log.info('complete release, r: %s | mb_id: %s' % (obj.name, mb_id)) inc = ('artists', 'url-rels', 'aliases', 'tags', 'recording-rels', 'work-rels', 'work-level-rels', 'artist-credits') url = 'http://%s/ws/2/release/%s/?fmt=json&inc=%s' % ( MUSICBRAINZ_HOST, mb_id, "+".join(inc)) r = requests.get(url) result = r.json() return obj def save(self, *args, **kwargs): self.clear_cache_file() unique_slugify(self, self.name) # convert approx date to real one ad = self.releasedate_approx try: ad_y = ad.year ad_m = ad.month ad_d = ad.day if ad_m == 0: ad_m = 1 if ad_d == 0: ad_d = 1 rd = datetime.strptime('%s/%s/%s' % (ad_y, ad_m, ad_d), '%Y/%m/%d') self.releasedate = rd except: self.releasedate = None if hasattr(self, '_last_editor') and getattr(self, '_last_editor'): last_editor = getattr(self, '_last_editor') self.last_editor = last_editor else: last_editor = None logger.debug('saved release id: {} - user: {} - caller: {}'.format( self.pk, last_editor, inspect.stack()[1][3])) super(Release, self).save(*args, **kwargs)
class Label(MigrationMixin, UUIDModelMixin, TimestampedModelMixin, models.Model): name = models.CharField(max_length=400) slug = AutoSlugField(populate_from='name', editable=True, blank=True, overwrite=True) labelcode = models.CharField(max_length=250, blank=True, null=True) address = models.TextField(blank=True, null=True) country = models.ForeignKey(Country, blank=True, null=True) email = models.EmailField(blank=True, null=True) phone = PhoneNumberField(blank=True, null=True) fax = PhoneNumberField(blank=True, null=True) main_image = models.ImageField(verbose_name=_('Logo Image'), upload_to=upload_image_to, storage=OverwriteStorage(), null=True, blank=True) description = extra.MarkdownTextField(blank=True, null=True) date_start = ApproximateDateField(verbose_name="Life-span begin", blank=True, null=True) date_end = ApproximateDateField(verbose_name="Life-span end", blank=True, null=True) owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="labels_owner") creator = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="labels_creator") last_editor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="labels_last_editor") publisher = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="labels_publisher") listed = models.BooleanField( verbose_name='Include in listings', default=True, help_text=_('Should this Label be shown on the default Label-list?')) disable_link = models.BooleanField( verbose_name='Disable Link', default=False, help_text=_('Disable Linking. Useful e.g. for "Unknown Label"')) disable_editing = models.BooleanField( verbose_name='Disable Editing', default=False, help_text=_('Disable Editing. Useful e.g. for "Unknown Label"')) type = models.CharField(verbose_name="Label type", max_length=128, default='unknown', choices=alibrary_settings.LABELTYPE_CHOICES) relations = GenericRelation('Relation') d_tags = tagging.fields.TagField(verbose_name="Tags", max_length=1024, blank=True, null=True) # refactoring parent handling parent = models.ForeignKey('self', null=True, blank=True, related_name='children') # identifiers ipi_code = models.CharField(verbose_name=_('IPI Code'), max_length=32, blank=True, null=True) isni_code = models.CharField(verbose_name=_('ISNI Code'), max_length=32, blank=True, null=True) objects = LabelManager() class Meta: app_label = 'alibrary' verbose_name = _('Label') verbose_name_plural = _('Labels') ordering = ('name', ) permissions = (('merge_label', 'Merge Labels'), ) def __unicode__(self): return self.name def get_folder(self, name): return def get_lookup_providers(self): providers = [] for key, name in LOOKUP_PROVIDERS: relations = self.relations.filter(service=key) relation = None if relations.count() == 1: relation = relations[0] providers.append({'key': key, 'name': name, 'relation': relation}) return providers def get_ct(self): return '{}.{}'.format(self._meta.app_label, self.__class__.__name__).lower() def get_absolute_url(self): if self.disable_link: return None try: return reverse('alibrary-label-detail', kwargs={ 'pk': self.pk, 'slug': self.slug, }) except NoReverseMatch: translation.activate('en') return reverse('alibrary-label-detail', kwargs={ 'pk': self.pk, 'slug': self.slug, }) def get_edit_url(self): return reverse("alibrary-label-edit", args=(self.pk, )) def get_admin_url(self): return reverse("admin:alibrary_label_change", args=(self.pk, )) def get_api_url(self): return reverse('api_dispatch_detail', kwargs={ 'api_name': 'v1', 'resource_name': 'library/label', 'pk': self.pk }) + '' def get_root(self): if not self.parent: return None if self.parent == self: return None parent = self.parent last_parent = None i = 0 while parent and i < 10: i += 1 parent = parent.parent if parent: last_parent = parent return last_parent def save(self, *args, **kwargs): unique_slugify(self, self.name) super(Label, self).save(*args, **kwargs)