For Developers

This section is targeting software developers. It is extremely technical. The purpose of it is to provide an overview of the code. Unless you are a developer, you should really not dig into this.

If you are a developer with no prior experience in this domain, you may find the documentation too vague. You will have to do some homework, most notably about data structure and validation in Common Works Registration, versions 2.1 and 3.0, as well as various royalty statement formats.

Examples can be found in tests (music_publisher.tests).

music_publisher

Django-Music-Publisher (DMP) is open source software for managing music metadata, registration/licencing of musical works and royalty processing.

music_publisher app is the only Django app in this project.

music_publisher.apps

Django app definition for music_publisher.

class music_publisher.apps.MusicPublisherConfig(app_name, app_module)

Bases: django.apps.config.AppConfig

Configuration for Music Publisher app.

label

app label

Type:str
name

app name

Type:str
verbose_name

app verbose name

Type:str
ready()

Validate settings when ready to prevent deployments with invalid settings.

music_publisher.validators

CWR-compatibility field-level validation.

For formats that allow dashes and dots (ISWC, IPI Base), the actual format is from CWR 2.x specification: ISWC without and IPI Base with dashes.

music_publisher.validators.check_ean_digit(ean)

EAN checksum validation.

Parameters:ean (str) – EAN
Raises:ValidationError
music_publisher.validators.check_iswc_digit(iswc, weight)

ISWC / IPI Base checksum validation.

Parameters:
  • iswc (str) – ISWC or IPI Base #
  • weight (int) – 1 for ISWC, 2 for IPI Base #
Raises:

ValidationError

music_publisher.validators.check_ipi_digit(all_digits)

IPI Name checksum validation.

Parameters:all_digits (str) – IPI Name #
Raises:ValidationError
music_publisher.validators.check_isni_digit(all_digits)

ISNI checksum validation.

Parameters:all_digits (str) – ISNI
Raises:ValidationError
class music_publisher.validators.CWRFieldValidator(field: str)

Bases: object

Validate fields for CWR compliance.

field

Validation service name of the field being validated

Type:str
deconstruct()

Return a 3-tuple of class import path, positional arguments, and keyword arguments.

music_publisher.validators.validate_settings()

CWR-compliance validation for settings.

This is used to prevent deployment with invalid settings.

music_publisher.base

Contains base (abstract) classes used in models

class music_publisher.base.TitleBase(*args, **kwargs)

Bases: django.db.models.base.Model

Abstract class for all classes that have a title.

title

Title, used in work title, alternate title, etc.

Type:django.db.models.CharField
class music_publisher.base.PersonBase(*args, **kwargs)

Bases: django.db.models.base.Model

Base class for all classes that contain people with first and last name.

This includes writers and artists. For bands, only the last name field is used.

first_name

First Name

Type:django.db.models.CharField
last_name

Last Name

Type:django.db.models.CharField
class music_publisher.base.SocietyAffiliationBase(*args, **kwargs)

Bases: django.db.models.base.Model

Abstract base for all objects with CMO affiliations

pr_society

Performing Rights Society Code

Type:django.db.models.CharField
mr_society

Mechanical Rights Society Code

Type:django.db.models.CharField
sr_society

Sync. Rights Society Code

Type:django.db.models.CharField
class music_publisher.base.IPIBase(*args, **kwargs)

Bases: django.db.models.base.Model

Abstract base for all objects containing IPI numbers.

ipi_base

IPI Base Number

Type:django.db.models.CharField
ipi_name

IPI Name Number

Type:django.db.models.CharField
_can_be_controlled

used to determine if there is enough data for a writer to be controlled.

Type:django.db.models.BooleanField
clean_fields(*args, **kwargs)

Data cleanup, allowing various import formats to be converted into consistently formatted data.

class music_publisher.base.IPIWithGeneralAgreementBase(*args, **kwargs)

Bases: music_publisher.base.IPIBase, music_publisher.base.SocietyAffiliationBase

Abstract base for all objects with general agreements.

saan

Society-assigned agreement number, in this context it is used for general agreements, for specific agreements use models.WriterInWork.saan.

Type:django.db.models.CharField
generally_controlled

flags if a writer is generally controlled (in all works)

Type:django.db.models.BooleanField
publisher_fee

this field is used in calculating publishing fees

Type:django.db.models.DecimalField
clean()

Clean the data and validate.

clean_fields(*args, **kwargs)

Data cleanup, allowing various import formats to be converted into consistently formatted data.

class music_publisher.base.ArtistBase(*args, **kwargs)

Bases: music_publisher.base.PersonBase

Performing artist base class.

isni

International Standard Name Id

Type:django.db.models.CharField
clean_fields(*args, **kwargs)

ISNI cleanup

class music_publisher.base.WriterBase(*args, **kwargs)

Bases: music_publisher.base.PersonBase, music_publisher.base.IPIWithGeneralAgreementBase

Base class for writers.

class music_publisher.base.LabelBase(*args, **kwargs)

Bases: django.db.models.base.Model

Music Label base class.

name

Label Name

Type:django.db.models.CharField
class music_publisher.base.LibraryBase(*args, **kwargs)

Bases: django.db.models.base.Model

Music Library base class.

name

Library Name

Type:django.db.models.CharField
class music_publisher.base.ReleaseBase(*args, **kwargs)

Bases: django.db.models.base.Model

Music Release base class

cd_identifier

CD Identifier, used when origin is library

Type:django.db.models.CharField
library

Library Name

Type:django.db.models.CharField
ean

EAN code

Type:django.db.models.CharField
release_date

Date of the release

Type:django.db.models.DateField
release_label

Label Name

Type:django.db.models.CharField
release_title

Title of the release

Type:django.db.models.CharField

music_publisher.models

Concrete models.

They mostly inherit from classes in base.

class music_publisher.models.Artist(*args, **kwargs)

Bases: music_publisher.base.ArtistBase

Performing artist.

get_dict()

Get the object in an internal dictionary format

Returns:internal dict format
Return type:dict
artist_id

Artist identifier

Returns:Artist ID
Return type:str
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.Label(*args, **kwargs)

Bases: music_publisher.base.LabelBase

Music Label.

label_id

Label identifier

Returns:Label ID
Return type:str
get_dict()

Get the object in an internal dictionary format

Returns:internal dict format
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.Library(*args, **kwargs)

Bases: music_publisher.base.LibraryBase

Music Library.

library_id

Library identifier

Returns:Library ID
Return type:str
get_dict()

Get the object in an internal dictionary format

Returns:internal dict format
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.Release(*args, **kwargs)

Bases: music_publisher.base.ReleaseBase

Music Release (album / other product)

library

Foreign key to models.Library

Type:django.db.models.ForeignKey
release_label

Foreign key to models.Label

Type:django.db.models.ForeignKey
recordings

M2M to models.Recording through models.Track

Type:django.db.models.ManyToManyField
release_id

Release identifier.

Returns:Release ID
Return type:str
get_dict(with_tracks=False)

Get the object in an internal dictionary format

Parameters:with_tracks (bool) – add track data to the output
Returns:internal dict format
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.LibraryReleaseManager

Bases: django.db.models.manager.Manager

Manager for a proxy class models.LibraryRelease

get_queryset()

Return only library releases

Returns:Queryset with instances of models.LibraryRelease
Return type:django.db.models.query.QuerySet
get_dict(qs)

Get the object in an internal dictionary format

Parameters:qs (django.db.models.query.QuerySet) –
Returns:internal dict format
Return type:dict
class music_publisher.models.LibraryRelease(*args, **kwargs)

Bases: music_publisher.models.Release

Proxy class for Library Releases (AKA Library CDs)

objects

Database Manager

Type:LibraryReleaseManager
clean()

Make sure that release title is required if one of the other “non-library” fields is present.

Raises:ValidationError – If not compliant.
get_origin_dict()

Get the object in an internal dictionary format.

This is used for work origin, not release data.

Returns:internal dict format
Return type:dict
exception DoesNotExist

Bases: music_publisher.models.DoesNotExist

exception MultipleObjectsReturned

Bases: music_publisher.models.MultipleObjectsReturned

class music_publisher.models.CommercialReleaseManager

Bases: django.db.models.manager.Manager

Manager for a proxy class models.CommercialRelease

get_queryset()

Return only commercial releases

Returns:Queryset with instances of models.CommercialRelease
Return type:django.db.models.query.QuerySet
get_dict(qs)

Get the object in an internal dictionary format

Parameters:qs (django.db.models.query.QuerySet) –
Returns:internal dict format
Return type:dict
class music_publisher.models.CommercialRelease(*args, **kwargs)

Bases: music_publisher.models.Release

Proxy class for Commercial Releases

objects

Database Manager

Type:CommercialReleaseManager
exception DoesNotExist

Bases: music_publisher.models.DoesNotExist

exception MultipleObjectsReturned

Bases: music_publisher.models.MultipleObjectsReturned

class music_publisher.models.OriginalPublishingAgreement(*args, **kwargs)

Bases: django.db.models.base.Model

Original Publishing Agreement for controlled writers.

Not used in DMP.

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.Writer(*args, **kwargs)

Bases: music_publisher.base.WriterBase

Writers.

original_publishing_agreement

Foreign key to models.OriginalPublishingAgreement

Type:django.db.models.ForeignKey
clean(*args, **kwargs)

Check if writer who is controlled still has enough data.

writer_id

Writer ID for CWR

Returns:formatted writer ID
Return type:str
get_dict()

Create a data structure that can be serialized as JSON.

Returns:JSON-serializable data structure
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.WorkManager

Bases: django.db.models.manager.Manager

Manager for class models.Work

get_queryset()

Get an optimized queryset.

Returns:Queryset with instances of models.Work
Return type:django.db.models.query.QuerySet
get_dict(qs)

Return a dictionary with works from the queryset

Parameters:qs (django.db.models.query import QuerySet) –
Returns:dictionary with works
Return type:dict
class music_publisher.models.Work(*args, **kwargs)

Bases: music_publisher.base.TitleBase

Concrete class, with references to foreign objects.

_work_id

permanent work id, either imported or fixed when exports are created

Type:django.db.models.CharField
iswc

ISWC

Type:django.db.models.CharField
original_title

title of the original work, implies modified work

Type:django.db.models.CharField
release_label

Foreign key to models.LibraryRelease

Type:django.db.models.ForeignKey
last_change

when the last change was made to this object or any of the child objects, basically used in filtering

Type:django.db.models.DateTimeField
artists

Artists performing the work

Type:django.db.models.ManyToManyField
writers

Writers who created the work

Type:django.db.models.ManyToManyField
objects

Database Manager

Type:WorkManager
work_id

Create Work ID used in registrations.

Returns:Internal Work ID
Return type:str
is_modification()

Check if the work is a modification.

Returns:True if modification, False if original
Return type:bool
clean_fields(*args, **kwargs)

Deal with various ways ISWC is written.

static get_publisher_dict()

Create data structure for the publisher.

Returns:JSON-serializable data structure
Return type:dict
get_dict(with_recordings=True)

Create a data structure that can be serialized as JSON.

Normalize the structure if required.

Returns:JSON-serializable data structure
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.AlternateTitle(*args, **kwargs)

Bases: music_publisher.base.TitleBase

Concrete class for alternate titles.

work

Foreign key to Work model

Type:django.db.models.ForeignKey
suffix

implies that the title should be appended to the work title

Type:django.db.models.BooleanField
get_dict()

Create a data structure that can be serialized as JSON.

Returns:JSON-serializable data structure
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.ArtistInWork(*args, **kwargs)

Bases: django.db.models.base.Model

Artist performing the work (live in CWR 3).

artist

FK to Artist

Type:django.db.models.ForeignKey
work

FK to Work

Type:django.db.models.ForeignKey
get_dict()
Returns:taken from models.Artist.get_dict()
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.WriterInWork(*args, **kwargs)

Bases: django.db.models.base.Model

Writers who created this work.

At least one writer in work must be controlled. Sum of relative shares must be (roughly) 100%. Capacity is limited to roles for original writers.

work

FK to Work

Type:django.db.models.ForeignKey
writer

FK to Writer

Type:django.db.models.ForeignKey
saan

Society-assigned agreement number between the writer and the original publisher, please note that this field is for SPECIFIC agreements, for a general agreement, use base.IPIBase.saan

Type:django.db.models.CharField
controlled

A complete mistery field

Type:django.db.models.BooleanField
relative_share

Initial split among writers, prior to publishing

Type:django.db.models.DecimalField
capacity

Role of the writer in this work

Type:django.db.models.CharField
publisher_fee

Percentage of royalties kept by publisher

Type:django.db.models.DecimalField
clean_fields(*args, **kwargs)

Turn SAAN into uppercase.

Parameters:
  • *args – passing through
  • **kwargs – passing through
Returns:

SAAN in uppercase

Return type:

str

clean()

Make sure that controlled writers have all the required data.

Also check that writers that are not controlled do not have data that can not apply to them.

get_agreement_dict()

Get agreement dictionary for this writer in work.

get_dict()

Create a data structure that can be serialized as JSON.

Returns:JSON-serializable data structure
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.Recording(*args, **kwargs)

Bases: django.db.models.base.Model

Recording.

release_date

Recording Release Date

Type:django.db.models.DateField
duration

Recording Duration

Type:django.db.models.TimeField
isrc

International Standard Recording Code

Type:django.db.models.CharField
record_label

Record Label

Type:django.db.models.CharField
clean_fields(*args, **kwargs)

ISRC cleaning, just removing dots and dashes.

Parameters:
  • *args – may be used in upstream
  • **kwargs – may be used in upstream
Returns:

return from django.db.models.Model.clean_fields()

complete_recording_title

Return complete recording title.

Returns:str
complete_version_title

Return complete version title.

Returns:str
recording_id

Create Recording ID used in registrations

Returns:Internal Recording ID
Return type:str
get_dict(with_releases=False, with_work=True)

Create a data structure that can be serialized as JSON.

Parameters:
  • with_releases (bool) – add releases data (through tracks)
  • with_work (bool) – add work data
Returns:

JSON-serializable data structure

Return type:

dict

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.Track(*args, **kwargs)

Bases: django.db.models.base.Model

Track, a recording on a release.

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.CWRExport(*args, **kwargs)

Bases: django.db.models.base.Model

Export in CWR format.

Common Works Registration format is a standard format for registration of musical works world-wide. Exports are available in CWR 2.1 revision 8 and CWR 3.0 (experimental).

nwr_rev

choice field where user can select which version and type of CWR it is

Type:django.db.models.CharField
cwr

contents of CWR file

Type:django.db.models.TextField
year

2-digit year format

Type:django.db.models.CharField
num_in_year

CWR sequential number in a year

Type:django.db.models.PositiveSmallIntegerField
works

included works

Type:django.db.models.ManyToManyField
description

internal note

Type:django.db.models.CharField
version

Return CWR version.

filename

Return CWR file name.

Returns:CWR file name
Return type:str
filename30

Return proper CWR 3.0 filename.

Format is: CWYYnnnnSUB_REP_VM - m - r.EXT

Returns:CWR file name
Return type:str
filename21

Return proper CWR 2.1 filename.

Returns:CWR file name
Return type:str
get_record(key, record)

Create CWR record (row) from the key and dict.

Parameters:
  • key (str) – type of record
  • record (dict) – field values
Returns:

CWR record (row)

Return type:

str

get_transaction_record(key, record)

Create CWR transaction record (row) from the key and dict.

This methods adds transaction and record sequences.

Parameters:
  • key (str) – type of record
  • record (dict) – field values
Returns:

CWR record (row)

Return type:

str

yield_iswc_request_lines(works)

Yield lines for an ISR (ISWC request) in CWR 3.x

yield_publisher_lines(controlled_relative_share)

Yield SPU/SPT lines.

Parameters:controlled_relative_share (Decimal) – sum of manuscript shares for controlled writers
Yields:str – CWR record (row/line)
yield_registration_lines(works)

Yield lines for CWR registrations (WRK in 3.x, NWR and REV in 2.x)

Parameters:works (list) – list of work dicts
Yields:str – CWR record (row/line)
yield_lines()

Yield CWR transaction records (rows/lines) for works

Parameters:works (query) – models.Work query
Yields:str – CWR record (row/line)
create_cwr()

Create CWR and save.

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.WorkAcknowledgement(*args, **kwargs)

Bases: django.db.models.base.Model

Acknowledgement of work registration.

date

Acknowledgement date

Type:django.db.models.DateField
remote_work_id

Remote work ID

Type:django.db.models.CharField
society_code

3-digit society code

Type:django.db.models.CharField
status

2-letter status code

Type:django.db.models.CharField
TRANSACTION_STATUS_CHOICES

choices for status

Type:tuple
work

FK to Work

Type:django.db.models.ForeignKey
get_dict()

Return dictionary with external work IDs.

Returns:JSON-serializable data structure
Return type:dict
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.ACKImport(*args, **kwargs)

Bases: django.db.models.base.Model

CWR acknowledgement file import.

date

Acknowledgement date

Type:django.db.models.DateField
filename

Description

Type:django.db.models.CharField
report

Basically a log

Type:django.db.models.CharField
society_code

3-digit society code, please note that choices is not set.

Type:models.CharField
society_name

Society name, used if society code is missing.

Type:models.CharField
exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class music_publisher.models.DataImport(*args, **kwargs)

Bases: django.db.models.base.Model

Importing basic work data from a CSV file.

This class just acts as log, the actual logic is in data_import.

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

music_publisher.cwr_templates

Django templates for CWR generation.

music_publisher.cwr_templates.TEMPLATES_21

Record templates for CWR 2.1

Type:dict
music_publisher.cwr_templates.TEMPLATES_30

Record templates for CWR 3.0

Type:dict

music_publisher.admin

Main interface for music_publisher.

All views are here, except for royalty_calculation.

class music_publisher.admin.MusicPublisherAdmin(model, admin_site)

Bases: django.contrib.admin.options.ModelAdmin

Parent class to all admin classes.

class music_publisher.admin.ArtistInWorkInline(parent_model, admin_site)

Bases: django.contrib.admin.options.TabularInline

Inline interface for models.ArtistInWork.

model

alias of music_publisher.models.ArtistInWork

class music_publisher.admin.RecordingInline(parent_model, admin_site)

Bases: django.contrib.admin.options.StackedInline

Inline interface for models.Recording, used in WorkAdmin.

model

alias of music_publisher.models.Recording

class music_publisher.admin.ArtistAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.Artist.

last_or_band(obj)

Placeholder for models.Artist.last_name.

save_model(request, obj, form, *args, **kwargs)

Save, then update last_change of the works whose CWR registration changes due to this change.

get_queryset(request)

Optimized queryset for changelist view.

work_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

recording_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

class music_publisher.admin.LabelAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.Label.

get_queryset(request)

Optimized queryset for changelist view.

commercialrelease_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

libraryrelease_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

recording_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

save_model(request, obj, form, *args, **kwargs)

Save, then update last_change of the corresponding works.

class music_publisher.admin.LibraryAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.Library.

get_queryset(request)

Optimized queryset for changelist view.

libraryrelease_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

work_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

save_model(request, obj, form, *args, **kwargs)

Save, then update last_change of the corresponding works.

class music_publisher.admin.TrackInline(parent_model, admin_site)

Bases: django.contrib.admin.options.TabularInline

Inline interface for models.Track, used in LibraryReleaseAdmin and CommercialReleaseAdmin.

model

alias of music_publisher.models.Track

class music_publisher.admin.ReleaseAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.Release.

has_module_permission(request)

Return False

has_add_permission(request)

Return False

has_change_permission(request, obj=None)

Return False

has_delete_permission(request, obj=None)

Return False

class music_publisher.admin.LibraryReleaseForm(*args, **kwargs)

Bases: django.forms.models.ModelForm

Custom form for models.LibraryRelease.

class music_publisher.admin.LibraryReleaseAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.LibraryRelease.

form

alias of LibraryReleaseForm

get_inline_instances(request, obj=None)

Limit inlines in popups.

save_model(request, obj, form, *args, **kwargs)

Save, then update last_change of the corresponding works.

get_queryset(request)

Optimized queryset for changelist view.

work_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

track_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

create_json(request, qs)

Batch action that downloads a JSON file containing library releases.

Returns:JSON file with selected works
Return type:JsonResponse
get_actions(request)

Custom action disabling the default delete_selected.

class music_publisher.admin.CommercialReleaseAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.CommercialRelease.

get_inline_instances(request, obj=None)

Limit inlines in popups.

get_queryset(request)

Optimized queryset for changelist view.

track_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

create_json(request, qs)

Batch action that downloads a JSON file containing comercial releases.

Returns:JSON file with selected commercial releases
Return type:JsonResponse
get_actions(request)

Custom action disabling the default delete_selected.

class music_publisher.admin.WriterAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Interface for models.Writer.

get_fieldsets(request, obj=None)

Return the fieldsets.

Depending on settings, MR and PR affiliations may not be needed. See WriterAdmin.get_society_list()

static get_society_list()

List which society fields are required.

Mechanical and Sync affiliation is not required if writers don’t collect any of it, which is the most usual case.

save_model(request, obj, form, *args, **kwargs)

Perform normal save_model, then update last_change of all connected works.

get_queryset(request)

Optimized queryset for changelist view.

work_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

class music_publisher.admin.AlternateTitleFormSet(data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None, **kwargs)

Bases: django.forms.models.BaseInlineFormSet

Formset for AlternateTitleInline.

clean()
Performs these checks:
if suffix is used, then validates the total length
Returns:None
Raises:ValidationError
class music_publisher.admin.AlternateTitleInline(parent_model, admin_site)

Bases: django.contrib.admin.options.TabularInline

Inline interface for models.AlternateTitle.

model

alias of music_publisher.models.AlternateTitle

formset

alias of AlternateTitleFormSet

complete_alt_title(obj)

Return the complete title, see models.AlternateTitle.__str__()

class music_publisher.admin.WriterInWorkFormSet(data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None, **kwargs)

Bases: django.forms.models.BaseInlineFormSet

Formset for WriterInWorkInline.

clean()
Performs these checks:
at least one writer must be controlled, at least one writer music be Composer or Composer&Lyricist sum of relative shares must be ~100%
Returns:None
Raises:ValidationError
class music_publisher.admin.WriterInWorkInline(parent_model, admin_site)

Bases: django.contrib.admin.options.TabularInline

Inline interface for models.WriterInWork.

model

alias of music_publisher.models.WriterInWork

formset

alias of WriterInWorkFormSet

class music_publisher.admin.WorkAcknowledgementInline(parent_model, admin_site)

Bases: django.contrib.admin.options.TabularInline

Inline interface for models.WorkAcknowledgement, used in WorkAdmin.

Note that normal users should only have a ‘view’ permission.

model

alias of music_publisher.models.WorkAcknowledgement

class music_publisher.admin.WorkForm(*args, **kwargs)

Bases: django.forms.models.ModelForm

Custom form for models.Work.

Calculate values for readonly field version_type.

class music_publisher.admin.WorkAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.Work.

This is by far the most important part of the interface.

actions

batch actions used: create_cwr(), create_json()

Type:tuple
inlines

inlines used in change view: AlternateTitleInline, WriterInWorkInline, RecordingInline, ArtistInWorkInline, WorkAcknowledgementInline,

Type:tuple
form

alias of WorkForm

writer_last_names(obj)

This is a standard way how writers are shown in other apps.

percentage_controlled(obj)

Controlled percentage (sum of relative shares for controlled writers)

Please note that writers in work are already included in the queryset for other reasons, so no overhead except summing.

work_id(obj)

Return models.Work.work_id, make it sortable.

cwr_export_count(obj)

Return the count of CWR exports with the link to the filtered changelist view for CWRExportAdmin.

recording_count(obj)

Return the count of CWR exports with the link to the filtered changelist view for CWRExportAdmin.

get_queryset(request)

Optimized queryset for changelist view.

class InCWRListFilter(request, params, model, model_admin)

Bases: django.contrib.admin.filters.SimpleListFilter

Custom list filter if work is included in any of CWR files.

lookups(request, model_admin)

Simple Yes/No filter

queryset(request, queryset)

Filter if in any of CWR files.

class ACKSocietyListFilter(request, params, model, model_admin)

Bases: django.contrib.admin.filters.SimpleListFilter

Custom list filter of societies from ACK files.

lookups(request, model_admin)

Simple Yes/No filter

queryset(request, queryset)

Filter on society sending ACKs.

class ACKStatusListFilter(request, params, model, model_admin)

Bases: django.contrib.admin.filters.SimpleListFilter

Custom list filter on ACK status.

lookups(request, model_admin)

Simple Yes/No filter

queryset(request, queryset)

Filter on ACK status.

class HasISWCListFilter(request, params, model, model_admin)

Bases: django.contrib.admin.filters.SimpleListFilter

Custom list filter on the presence of ISWC.

lookups(request, model_admin)

Simple Yes/No filter

queryset(request, queryset)

Filter on presence of iswc.

class HasRecordingListFilter(request, params, model, model_admin)

Bases: django.contrib.admin.filters.SimpleListFilter

Custom list filter on the presence of recordings.

lookups(request, model_admin)

Simple Yes/No filter

queryset(request, queryset)

Filter on presence of models.Recording.

get_search_results(request, queryset, search_term)

Deal with the situation term is work ID.

save_model(request, obj, form, *args, **kwargs)

Set last_change if the work form has changed.

save_formset(request, form, formset, change)

Set last_change for the work if any of the inline forms has changed.

create_cwr(request, qs)

Batch action that redirects to the add view for CWRExportAdmin with selected works.

create_json(request, qs)

Batch action that downloads a JSON file containing selected works.

Returns:JSON file with selected works
Return type:JsonResponse
get_labels_for_csv(works)

Return the list of labels for the CSV file.

get_rows_for_csv(works)

Return rows for the CSV file, including the header.

create_csv(request, qs)

Batch action that downloads a CSV file containing selected works.

Returns:JSON file with selected works
Return type:JsonResponse
get_actions(request)

Custom action disabling the default delete_selected.

get_inline_instances(request, obj=None)

Limit inlines in popups.

class music_publisher.admin.RecordingAdmin(model, admin_site)

Bases: music_publisher.admin.MusicPublisherAdmin

Admin interface for models.Recording.

class HasISRCListFilter(request, params, model, model_admin)

Bases: django.contrib.admin.filters.SimpleListFilter

Custom list filter on the presence of ISRC.

lookups(request, model_admin)

Simple Yes/No filter

queryset(request, queryset)

Filter on presence of iswc.

get_queryset(request)

Optimized query regarding work name

title(obj)

Return the recording title, which is not the necessarily the title field.

Link to the work the recording is based on.

Link to the recording artist.

Link to the recording label.

class music_publisher.admin.CWRExportAdmin(model, admin_site)

Bases: django.contrib.admin.options.ModelAdmin

Admin interface for models.CWRExport.

work_count(obj)

Return the work count from the database field, or count them. (dealing with legacy)

get_preview(obj)

Get CWR preview.

If you are using highlighing, then override this method.

Link to the CWR preview.

Link for downloading CWR file.

get_queryset(request)

Optimized query with count of works in the export.

get_readonly_fields(request, obj=None)

Read-only fields differ if CWR has been completed.

get_fields(request, obj=None)

Shown fields differ if CWR has been completed.

has_add_permission(request)

Return false if CWR delivery code is not present.

has_delete_permission(request, obj=None)

If CWR has been created, it can no longer be deleted, as it may have been sent. This may change once the delivery is automated.

has_change_permission(request, obj=None)

If object exists, it can only be edited in changelist.

get_form(request, obj=None, **kwargs)

Set initial values for work IDs.

add_view(request, form_url='', extra_context=None, work_ids=None)

Added work_ids as default for wizard from WorkAdmin.create_cwr().

change_view(request, object_id, form_url='', extra_context=None)

Normal change view with two sub-views defined by GET parameters:

Parameters:
  • preview – that returns the preview of CWR file,
  • download – that downloads the CWR file.

save_model() passes the main object, which is needed to fetch CWR from the external service, but only after related objects are saved.

class music_publisher.admin.ACKImportForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)

Bases: django.forms.models.ModelForm

Form used for CWR acknowledgement imports.

acknowledgement_file

Field for file upload

Type:FileField
clean()

Perform usual clean, then process the file, returning the content field as if it was the TextField.

class music_publisher.admin.AdminWithReport(model, admin_site)

Bases: django.contrib.admin.options.ModelAdmin

The parent class for all admin classes with a report field.

print_report(obj)

Mark report as HTML-safe.

class music_publisher.admin.ACKImportAdmin(model, admin_site)

Bases: music_publisher.admin.AdminWithReport

Admin interface for models.ACKImport.

get_form(request, obj=None, **kwargs)

Returns a custom form for new objects, default one for changes.

get_fields(request, obj=None)

Return different fields for add vs change.

process(request, society_code, file_content, import_iswcs=False)

Create appropriate WorkAcknowledgement objects, without duplicates.

Big part of this code should be moved to the model, left here because messaging is simpler.

save_model(request, obj, form, change)

Custom save_model, it ignores changes, validates the form for new instances, if valid, it processes the file and, upon success, calls super().save_model.

has_add_permission(request)

Return false if CWR delivery code is not present.

has_delete_permission(request, obj=None, *args, **kwargs)

Deleting ACK imports is a really bad idea.

has_change_permission(request, obj=None)

Deleting this would make no sense, since the data is processed.

get_preview(obj)

Get CWR preview.

If you are using highlighing, then override this method.

Link to CWR ACK preview.

change_view(request, object_id, form_url='', extra_context=None)

Normal change view with a sub-view defined by GET parameters:

Parameters:preview – that returns the preview of CWR file.
class music_publisher.admin.DataImportForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)

Bases: django.forms.models.ModelForm

Form used for data imports.

data_file

Field for file upload

Type:FileField
clean()

This is the actual import process, if all goes well, the report is saved.

Raises:ValidationError
class music_publisher.admin.DataImportAdmin(model, admin_site)

Bases: music_publisher.admin.AdminWithReport

Data import from CSV files.

Only the interface is here, the whole logic is in data_import.

form

alias of DataImportForm

get_fields(request, obj=None)

Return different fields for add vs change.

has_delete_permission(request, obj=None, *args, **kwargs)

Deleting data imports is a really bad idea.

has_change_permission(request, obj=None)

Deleting this would make no sense, since the data is processed.

get_form(request, obj=None, change=False, **kwargs)

Return a Form class for use in the admin add view. This is used by add_view and change_view.

save_model(request, obj, form, change)

Custom save_model, it ignores changes, validates the form for new instances, if valid, it processes the file and, upon success, calls super().save_model.

music_publisher.data_import

All the code related to importing data from external files.

Currently, only works (with writers, artists, library data and ISRCs) are imported. (ISRCs will be used for importing recording data the in future.)

class music_publisher.data_import.DataImporter(filelike, user=None)

Bases: object

log(action_flag, obj, message)

Helper function for logging history.

static get_clean_key(value, tup, name)

Try to match either key or value from a user input mess.

process_writer_value(key, key_elements, value)

Clean a value for a writer and return it.

If it is a ‘controlled’, then also calculate general agreement. Always return a tuple.

unflatten(in_dict)

Create a well-structured dictionary with cleaner values.

run()

Run the import as atomic.

music_publisher.royalty_calculation

This module is about processing royalty statements.

It processes files in the request-response cycle, not in background workers. Therefore, focus is on speed. Nothing is written to the database, and SELECTs are optimised and performed in one batch.

music_publisher.royalty_calculation.get_id_sources()

Yield choices, fixed and societies.

music_publisher.royalty_calculation.get_right_types()

Yield fixed options.

They will be extended with columns in JS and prior to validation.

class music_publisher.royalty_calculation.RoyaltyCalculationForm(*args, **kwargs)

Bases: django.forms.forms.Form

The form for royalty calculations.

is_valid()

Append additional choices to various fields, prior to the actual validation.

class music_publisher.royalty_calculation.RoyaltyCalculation(form)

Bases: object

The process of royalty calculation.

filename

Return the filename of the output file.

fieldnames

Return the list of field names in the output file.

get_work_ids()

Find work unambiguous work identifiers.

Returns:set of work identifier from the file
get_work_queryset(work_ids)

Return the appropriate queryset based on work ID source and ids.

Returns:queryset with models.WriterInWork objects. query_id has the matched field value.
generate_works_dict(qs)

Generate the works cache.

Returns:dict (works) of lists (writerinwork) of dicts
generate_writer_dict()

Generate the writers cache. :returns: dict (writer) of dicts

get_works_and_writers()

Get work and writer data.

Extract all work IDs, then perform the queries and put them in dictionaries. When the actual file processing happens, no further queries are required.

process_row(row)

Process one incoming row, yield multiple output rows.

out_file_path

This method creates the output file and outputs the temporary path.

Note that the process happens is several passes.

class music_publisher.royalty_calculation.RoyaltyCalculationView(**kwargs)

Bases: django.contrib.auth.mixins.PermissionRequiredMixin, django.views.generic.edit.FormView

The view for royalty calculations.

form_class

alias of RoyaltyCalculationForm

render_to_response(context, **response_kwargs)

Prepare the context, required since we use admin template.

dispatch(request, *args, **kwargs)

Royalty processing works only with TemporaryFileUploadHandler.

form_valid(form)

This is where the magic happens.

music_publisher.tests

Tests for music_publisher.

The folder includes these files:

  • CW200001DMP_000.V21 - CWR 2.1 registration file
  • CW200002DMP_0000_V3-0-0.SUB - CWR 3.0 registration file
  • CW200003DMP_0000_V3-0-0.ISR - CWR3.0 ISWC request file
  • CW200001052_DMP.V21 - CWR 2.1 acknowledgement file
  • dataimport.csv - used for data imports
  • royaltystatement.csv - CSV royalty statement
  • royaltystatement_200k_rows.csv - CSV royalty statement with 200.000 rows, used for load testing.

Actual tests are in music_publisher.tests.tests.

music_publisher.tests.tests

Tests for music_publisher.

This software has almost full test coverage. The only exceptions are instances of Exception being caught during data imports. (User idiocy is boundless.)

Most of the tests are functional end-to-end tests. While they test that code works, they don’t always test that it works as expected.

Still, it must be noted that exports are tested against provided templates (made in a different software, not using the same code beyond Python standard library).

More precise tests would be better.

music_publisher.tests.tests.get_data_from_response(response)

Helper for extracting data from HTTP response in a way that can be fed back into POST that works with Django Admin.

class music_publisher.tests.tests.DataImportTest(methodName='runTest')

Bases: django.test.testcases.TestCase

Functional test for data import from CSV files.

classmethod setUpClass()

Hook method for setting up class fixture before running tests in the class.

test_log()

Test logging during import.

test_unknown_key_exceptions()

Test exceptions not tested in functional tests.

class music_publisher.tests.tests.AdminTest(methodName='runTest')

Bases: django.test.testcases.TestCase

Functional tests on the interface, and several related unit tests.

Note that tests build one atop another, simulating typical work flow.

classmethod create_original_work()

Create original work, three writers, one controlled, with recording, alternate titles, included in a commercial release.

classmethod create_modified_work()

Create modified work, original writer plus arranger, with recording, alternate titles.

classmethod create_copublished_work()

Create work, two writers, one co-published.

classmethod create_writers()

Create four writers with different properties.

classmethod create_cwr2_export()

Create a NWR and a REV CWR2 Export.

classmethod create_cwr3_export()

Create a WRK and an ISR CWR3 Export.

classmethod setUpClass()

Class setup.

Creating users. Creating instances of classes of less importance:

  • label,
  • library,
  • artist,
  • releases,

then calling the methods above.

test_strings()

Test __str__ methods for created objects.

test_unknown_user()

Several fast test to make sure that an unregistered user is blind.

test_super_user()

Testing index for superuser covers all the cases.

test_staff_user()

Test that a staff user can access some urls.

Please note that most of the work is in other tests.

test_cwr_previews()

Test that CWR preview works.

test_cwr_downloads()

Test that the CWR file can be downloaded.

test_json()

Test that JSON export works.

test_cwr_nwr()

Test that CWR export works.

test_csv()

Test that CSV export works.

test_label_change()

Test that models.Label objects can be edited.

test_library_change()

Test that models.Library objects can be edited.

test_artist_change()

Test that models.Artist objects can be edited.

test_commercialrelease_change()

Test that models.CommercialRelease can be edited.

test_libraryrelease_change()

Test that models.LibraryRelease can be edited.

test_audit_user()

Test that audit user can see, but not change things.

test_generally_controlled_not_controlled()

Test that a controlled flag must be set for a writer who is generally controlled.

test_generally_controlled_missing_capacity()

Test that if controlled flag is set, the capacity must be set as well.

test_controlled_but_no_writer()

Test that a line without a writer can not have controlled set.

test_controlled_but_missing_data()

The requirements for a controlled writer are higher, make sure they are obeyed when setting a writer as controlled.

test_controllable_and_controlled_but_missing_saan()

If SAAN is required, then it must be set in the Writer object, or in the WriterInWork object or both.

test_controllable_and_controlled_but_missing_fee()

If publisher_fee is required, then it must be set in the Writer, or in the WriterInWork object or both.

test_writer_switch()

Just replace one writer with another, just to test last change

test_not_controlled_extra_saan()

SAAN can not be set if a writer is not controlled.

test_not_controlled_extra_fee()

Publisher fee can not be set if a writer is not controlled.

test_bad_alt_title()

Test that alternate title can not have disallowed characters.

test_unallowed_capacity()

Some capacieties are allowed only in modifications.

test_missing_capacity()

At least one of the additional capacieties must be set for modifications.

test_none_controlled()

At least one Writer in Work line must be set as controlled.

test_wrong_sum_of_shares()

Sum of shares must be (roughly) 100%

test_wrong_capacity_in_copublishing_modification()

Test the situation where one writer appears in two rows, once as controlled, once as not with different capacities.

test_altitle_sufix_too_long()

A suffix plus the base title plus one space in between must be 60 characters or less.

test_ack_import_and_work_filters()

Test acknowledgement import and then filters on the change view, as well as other related views.

These tests must be together, ack import is used in filters.

test_data_import_and_royalty_calculations()

Test data import, ack import and royalty calculations.

This is the normal process, work data is entered, then the registration follows and then it can be processed in royalty statements.

This test also includes load testing, 200.000 rows must be imported in under 10-15 seconds, performed 4 times with different algos and ID types.

test_bad_data_import()

Test bad data import.

test_recording_filters()

Test Work changelist filters.

Test Work search.

test_simple_save()

Test saving changed Work form.

test_create_cwr_wizard()

Test if CWR creation action works as it should.

test_create_cwr_wizard_no_publisher_code()

Publisher code is required for CWR generation, it must fail if attempted otherwise.

class music_publisher.tests.tests.CWRTemplatesTest(methodName='runTest')

Bases: django.test.testcases.SimpleTestCase

A test related to CWR Templates.

test_templates()

Test CWR 2.1 and 3.0 generation with empty values.

class music_publisher.tests.tests.ValidatorsTest(methodName='runTest')

Bases: django.test.testcases.TestCase

Test all validators.

Note that validators are also validating settings.

class music_publisher.tests.tests.ModelsSimpleTest(methodName='runTest')

Bases: django.test.testcases.TransactionTestCase

These tests are modifying objects directly.

test_work()

A complex test where a complete Work objects with all related objects is created.