# For copyright and license terms, see COPYRIGHT.rst (top level of repository)
# Repository: https://github.com/C3S/collecting_society_web
import logging
import colander
import deform
from portal_web.models import Tdb
from portal_web.views.forms import (
FormController,
deferred_file_upload_widget
)
from ...services import (_, picture_processing)
from ...models import (
Artist,
Creation,
Release,
License,
Genre,
Style,
Label,
Publisher
)
from .datatables import (
ArtistSequence,
TrackSequence,
LabelSequence,
PublisherSequence
)
log = logging.getLogger(__name__)
# --- Controller --------------------------------------------------------------
[docs]
class AddRelease(FormController):
"""
form controller for add release
"""
[docs]
def controller(self):
self.form = add_release_form(self.request)
self.render()
if self.submitted() and self.validate():
self.create_release()
return self.response
# --- Stages --------------------------------------------------------------
# --- Conditions ----------------------------------------------------------
# --- Actions -------------------------------------------------------------
[docs]
@Tdb.transaction(readonly=False)
def create_release(self):
appstruct = self.appstruct
web_user = self.request.web_user
party = self.request.party
# generate vlist
_release = {
'entity_origin':
"direct",
'entity_creator':
party,
'type':
appstruct['metadata']['release_type'],
'title':
appstruct['metadata']['release_title'],
'genres':
[('add', map(int, appstruct['metadata']['genres']))],
'styles':
[('add', map(int, appstruct['metadata']['styles']))],
'warning':
appstruct['metadata']['warning'],
'grid_code':
appstruct['production']['grid_code'],
'copyright_date':
appstruct['production']['copyright_date'],
'production_date':
appstruct['production']['production_date'],
'label_catalog_number':
appstruct['distribution']['label_catalog_number'],
'ean_upc_code':
appstruct['distribution']['ean_upc_code'],
'release_date':
appstruct['distribution']['release_date'],
'release_cancellation_date':
appstruct['distribution']['release_cancellation_date'],
'online_release_date':
appstruct['distribution']['online_release_date'],
'online_cancellation_date':
appstruct['distribution']['online_cancellation_date'],
'distribution_territory':
appstruct['distribution']['distribution_territory'],
}
# artist realease: artist
if appstruct['metadata']['release_type'] == 'artist':
oid = appstruct['metadata']['artist']
artist = Artist.search_by_oid(oid)
if artist and artist.permits(web_user, 'edit_artist'):
_release['artists'] = [('add', [artist.id])]
# split realease: split_artists
if appstruct['metadata']['release_type'] == 'split_release':
_add_artists = []
for _artist in appstruct['metadata']['artists']:
artist = Artist.search_by_oid(artist['oid'])
if artist:
_add_artists.append(artist.id)
_release['artists'] = [('add', _add_artists)]
# tracks
tracks_create = []
for medium_number, _medium in enumerate(appstruct['tracks']['media']):
if _medium:
for track_number, _track in enumerate(_medium):
_creation = _track['track'][0]
license = License.search_by_oid(_track['license'])
if not license:
continue
# create track
if _track['mode'] == "create":
# create creation
if _creation['mode'] == "create":
creation = Creation.create_foreign(
party,
_creation['artist'],
_creation['titlefield']
)
if not creation:
continue
# add creation
else:
creation = Creation.search_by_oid(_creation['oid'])
if not creation:
continue
# append track
tracks_create.append({
'creation': creation.id,
'title': _track['track_title'],
'medium_number': medium_number + 1,
'track_number': track_number + 1,
'license': license.id
})
# append actions
_release['tracks'] = []
if tracks_create:
_release['tracks'].append(('create', tracks_create))
# publisher
_publisher = next(iter(appstruct['production']['publisher']), None)
if _publisher:
if _publisher['mode'] == "add":
publisher = Publisher.search_by_oid(_publisher['oid'])
if publisher:
_release['publisher'] = publisher.id
if _publisher['mode'] == "create":
_release['label_name'] = _publisher['name']
# label
_label = next(iter(appstruct['distribution']['label']), None)
if _label:
if _label['mode'] == "add":
label = Label.search_by_oid(_label['oid'])
if label:
_release['label'] = label.id
if _label['mode'] == "create":
_release['label_name'] = _label['name']
# picture
if self.appstruct['metadata']['picture']:
err, p, t, m = picture_processing(
self.appstruct['metadata']['picture']['fp'])
if not err:
_release['picture_data'] = p
_release['picture_thumbnail_data'] = t
_release['picture_data_mime_type'] = m
else:
self.request.session.flash(err, 'main-alert-warning')
# remove empty fields
for index, value in _release.items():
if not value:
del _release[index]
# create release
release = Release.create([_release])
# user feedback
if not release:
log.info("release add failed for %s: %s" % (web_user, _release))
self.request.session.flash(
_("Release could not be added: ${reti}",
mapping={'reti': _release['title']}),
'main-alert-danger'
)
self.redirect()
return
release = release[0]
log.info("release add successful for %s: %s" % (web_user, release))
self.request.session.flash(
_("Release added: ${reti} (${reco})",
mapping={'reti': release.title,
'reco': release.code}),
'main-alert-success'
)
# redirect
self.redirect()
# --- Validators --------------------------------------------------------------
# --- Options -----------------------------------------------------------------
release_type_options = (
('artist', _('Artist Release')),
('split', _('Split Release')),
('compilation', _('Compilation')),
)
# --- Widgets -----------------------------------------------------------------
@colander.deferred
def current_artists_select_widget(node, kw):
request = kw.get('request')
artists = Artist.current_editable(request)
artist_options = [(artist.oid, artist.name) for artist in artists]
widget = deform.widget.Select2Widget(values=artist_options)
return widget
@colander.deferred
def deferred_genre_widget(node, kw):
genres = Genre.search_all()
genre_options = [(genre.id, str(genre.name)) for genre in genres]
widget = deform.widget.Select2Widget(values=genre_options, multiple=True)
return widget
@colander.deferred
def deferred_style_widget(node, kw):
styles = Style.search_all()
style_options = [(style.id, str(style.name)) for style in styles]
widget = deform.widget.Select2Widget(values=style_options, multiple=True)
return widget
# --- Fields ------------------------------------------------------------------
@colander.deferred
def deferred_artist_missing(node, kw):
params = kw['request'].params
release_type = params.get("release_type")
if not release_type or release_type == 'artist':
return colander.required
return ""
@colander.deferred
def deferred_split_artists_missing(node, kw):
params = kw['request'].params
release_type = params.get("release_type")
if not release_type or release_type == 'split':
return colander.required
return ""
# -- Metadata tab --
[docs]
class ReleaseTypeField(colander.SchemaNode):
oid = "type"
schema_type = colander.String
widget = deform.widget.Select2Widget(values=release_type_options)
[docs]
class ArtistField(colander.SchemaNode):
oid = "artist"
schema_type = colander.String
validator = colander.uuid
widget = current_artists_select_widget
missing = deferred_artist_missing
[docs]
class SplitArtistSequence(ArtistSequence):
min_len = 1
missing = deferred_split_artists_missing
[docs]
class ReleaseTitleField(colander.SchemaNode):
oid = "title"
schema_type = colander.String
[docs]
class GenreCheckboxField(colander.SchemaNode):
oid = "genres"
schema_type = colander.Set
widget = deferred_genre_widget
validator = colander.Length(min=1)
[docs]
class StyleCheckboxField(colander.SchemaNode):
oid = "styles"
schema_type = colander.Set
widget = deferred_style_widget
validator = colander.Length(min=1)
[docs]
class WarningField(colander.SchemaNode):
oid = "warning"
schema_type = colander.String
missing = ""
[docs]
class PictureField(colander.SchemaNode):
oid = "picture"
schema_type = deform.FileData
widget = deferred_file_upload_widget
missing = ""
# -- Production Tab --
[docs]
class CopyrightDateField(colander.SchemaNode):
oid = "copyright_date"
schema_type = colander.Date
missing = ""
# class CopyrightOwnerField(colander.SchemaNode):
# oid = "get_copyright_owners"
# schema_type = colander.String
# # widget = party_select_widget <- no paries any more?
# widget = deform.widget.TextInputWidget(readonly=True)
# missing = colander.null
# # displaying a read-only function field, assembled from the repective
# # copyright owners of the release creations
[docs]
class ProductionDateField(colander.SchemaNode):
oid = "production_date"
schema_type = colander.Date
missing = ""
# -- Distribution tab --
[docs]
class LabelCatalogNumberField(colander.SchemaNode):
oid = "label_catalog_number"
schema_type = colander.String
missing = ""
[docs]
class EanUpcCodeField(colander.SchemaNode):
oid = "ean_upc_code"
schema_type = colander.String
validator = colander.All(
# TODO: find out why this is not working (old colander version?):
# min_err=_('at least 12 digits for a valid UPC barcode,
# '13 for an EAN code.'),
# max_err=_('maximum of 13 digits for an EAN code (12 for a UPC).'
colander.Length(min=12, max=13),
# colander.ContainsOnly(
# '0123456789',
# err_msg=_('may only contain digits') <- why no custom err_msg?!?
# )
colander.Regex('^[0-9]*$', msg=_('May only contain digits'))
)
missing = ""
[docs]
class GRidCodeField(colander.SchemaNode):
oid = "grid_code"
schema_type = colander.String
validator = colander.Regex(
'^A1-[a-zA-Z0-9]{5}-[a-zA-Z0-9]{10}-[a-zA-Z0-9]*$',
msg=_('Please enter a valid '
'Global Release Identifier, for example: '
'A1-2425G-ABC1234002-M')
)
missing = ""
[docs]
class ReleaseDateField(colander.SchemaNode):
oid = "release_date"
schema_type = colander.Date
missing = ""
[docs]
class ReleaseCancellationDateField(colander.SchemaNode):
oid = "release_cancellation_date"
schema_type = colander.Date
missing = ""
[docs]
class OnlineReleaseDateField(colander.SchemaNode):
oid = "online_release_date"
schema_type = colander.Date
missing = ""
[docs]
class OnlineReleaseCancellationDateField(colander.SchemaNode):
oid = "online_cancellation_date"
schema_type = colander.Date
missing = ""
[docs]
class DistributionTerritoryField(colander.SchemaNode):
oid = "distribution_territory"
schema_type = colander.String
missing = ""
# --- Schemas -----------------------------------------------------------------
[docs]
class TracksSchema(colander.Schema):
widget = deform.widget.MappingWidget(template='navs/mapping')
media = MediaSequence(actions=['create', 'edit'])
[docs]
class ProductionSchema(colander.Schema):
widget = deform.widget.MappingWidget(template='navs/mapping')
publisher = PublisherSequence(title=_("Publisher"), max_len=1)
grid_code = GRidCodeField(title=_("GRid Code"))
copyright_date = CopyrightDateField(title=_("Copyright Date"))
# copyright_owner = CopyrightOwnerField(title=_(u"Copyright Owner(s)"))
production_date = ProductionDateField(title=_("Production Date"))
[docs]
class DistributionSchema(colander.Schema):
widget = deform.widget.MappingWidget(template='navs/mapping')
label = LabelSequence(title=_("Label"), max_len=1)
label_catalog_number = LabelCatalogNumberField(
title=_("Label Catalog Number of Release"))
ean_upc_code = EanUpcCodeField(title=_("EAN or UPC Code"))
release_date = ReleaseDateField(title=_("Release Date"))
release_cancellation_date = ReleaseCancellationDateField(
title=_("Release Cancellation Date"))
online_release_date = OnlineReleaseDateField(
title=_("Online Release Date"))
online_cancellation_date = OnlineReleaseCancellationDateField(
title=_("Online Release Cancellation Date"))
distribution_territory = DistributionTerritoryField(
title=_("Distribution Territory"))
[docs]
class AddReleaseSchema(colander.Schema):
widget = deform.widget.FormWidget(template='navs/form', navstyle='tabs')
metadata = MetadataSchema(title=_("Metadata"))
production = ProductionSchema(title=_("Production"))
distribution = DistributionSchema(title=_("Distribution"))
tracks = TracksSchema(title=_("Tracks"))
# --- Forms -------------------------------------------------------------------