From 1d63f73943bc240e216c1c18bfad8a3b19a3e00f Mon Sep 17 00:00:00 2001 From: Joseph Muller Date: Fri, 6 Dec 2024 15:55:11 +0000 Subject: [PATCH] Updates to data model #3168 --- src/core/admin.py | 39 +++++++++- ...ization_remove_account_country_and_more.py | 9 ++- src/core/models.py | 72 ++++++++++++++++--- ...ove_preprintauthor_affiliation_and_more.py | 2 +- ...80_remove_frozenauthor_country_and_more.py | 2 +- .../0035_rorimport_rorimporterror.py | 2 +- src/utils/models.py | 4 +- 7 files changed, 111 insertions(+), 19 deletions(-) diff --git a/src/core/admin.py b/src/core/admin.py index 531fdcc58b..a70079d543 100755 --- a/src/core/admin.py +++ b/src/core/admin.py @@ -400,11 +400,11 @@ class AccessRequestAdmin(admin.ModelAdmin): class OrganizationAdmin(admin.ModelAdmin): list_display = ('pk', 'ror', '_ror_display', '_custom_label', - '_locations', 'ror_status') + 'website', '_locations', 'ror_status') list_display_links = ('pk', 'ror') list_filter = ('ror_status', 'locations__country') search_fields = ('pk', 'ror_display__value', 'custom_label__value', 'labels__value', - 'aliases__value', 'acronyms__value') + 'aliases__value', 'acronyms__value', 'website', 'ror') raw_id_fields = ('locations', ) def _ror_display(self, obj): @@ -442,6 +442,40 @@ class LocationAdmin(admin.ModelAdmin): 'geonames_id') +class AffiliationAdmin(admin.ModelAdmin): + list_display = ('pk', '_person', 'organization', + 'title', 'department', 'start', 'end') + list_display_links = ('pk', '_person') + list_filter = ('start', 'end', 'organization__locations__country') + search_fields = ( + 'pk', + 'title', + 'department', + 'organization__ror_display_for', + 'organization__custom_label_for', + 'organization__label_for', + 'organization__alias_for', + 'organization__acronym_for' + 'account__first_name', + 'account__last_name', + 'account__email', + 'frozen_author__first_name', + 'frozen_author__last_name', + 'frozen_author__frozen_email', + 'preprint_author__account__first_name', + 'preprint_author__account__last_name', + 'preprint_author__account__email', + ) + raw_id_fields = ('account', 'frozen_author', + 'preprint_author', 'organization') + + def _person(self, obj): + if obj: + return obj.account or obj.frozen_author or obj.preprint_author + else: + return '' + + admin_list = [ (models.AccountRole, AccountRoleAdmin), (models.Account, AccountAdmin), @@ -474,6 +508,7 @@ class LocationAdmin(admin.ModelAdmin): (models.Organization, OrganizationAdmin), (models.OrganizationName, OrganizationNameAdmin), (models.Location, LocationAdmin), + (models.Affiliation, AffiliationAdmin), ] [admin.site.register(*t) for t in admin_list] diff --git a/src/core/migrations/0097_location_organization_remove_account_country_and_more.py b/src/core/migrations/0097_location_organization_remove_account_country_and_more.py index 552081efb6..d56d103940 100644 --- a/src/core/migrations/0097_location_organization_remove_account_country_and_more.py +++ b/src/core/migrations/0097_location_organization_remove_account_country_and_more.py @@ -9,9 +9,9 @@ class Migration(migrations.Migration): dependencies = [ - ('submission', '0080_remove_frozenauthor_country_and_more'), ('repository', '0044_remove_preprintauthor_affiliation_and_more'), - ('core', '0096_update_review_ack_email'), + ('submission', '0080_remove_frozenauthor_country_and_more'), + ('core', '0099_alter_accountrole_options'), ] operations = [ @@ -32,6 +32,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('ror', models.URLField(blank=True, help_text='Research Organization Registry identifier (URL)', validators=[core.models.validate_ror], verbose_name='ROR')), ('ror_status', models.CharField(blank=True, choices=[('active', 'Active'), ('inactive', 'Inactive'), ('withdrawn', 'Withdrawn'), ('unknown', 'Unknown')], default='unknown', max_length=10)), + ('website', models.CharField(blank=True, max_length=500)), ('locations', models.ManyToManyField(blank=True, null=True, to='core.location')), ], ), @@ -78,4 +79,8 @@ class Migration(migrations.Migration): 'ordering': ['-pk'], }, ), + migrations.AddConstraint( + model_name='organization', + constraint=models.UniqueConstraint(condition=models.Q(('ror__exact', ''), _negated=True), fields=('ror',), name='filled_unique'), + ), ] diff --git a/src/core/models.py b/src/core/models.py index c142ea6c12..d955e26bcb 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -402,6 +402,10 @@ def initials(self): def affiliation(self, obj=False, date=None): return Affiliation.get_primary(account=self, obj=obj, date=date) + @property + def affiliations(self): + return Affiliation.objects.filter(account=self).order_by('-is_primary') + @property def institution(self): affil = self.affiliation(obj=True) @@ -1975,7 +1979,6 @@ def validate_ror(url): raise ValidationError(f'{ror} is not a valid ROR identifier') - class Organization(models.Model): class RORStatus(models.TextChoices): @@ -1996,12 +1999,32 @@ class RORStatus(models.TextChoices): choices=RORStatus.choices, default=RORStatus.UNKNOWN, ) + website = models.CharField( + blank=True, + max_length=500, + ) locations = models.ManyToManyField( 'Location', blank=True, null=True, ) + class Meta: + constraints = [ + models.UniqueConstraint( + fields=['ror'], + condition=~models.Q(ror__exact=''), + name='filled_unique', + ) + ] + + def __str__(self): + elements = [ + str(self.name) if self.name else '', + str(self.location) if self.location else '', + ] + return ', '.join([element for element in elements if element]) + @property def name(self): """ @@ -2018,13 +2041,6 @@ def name(self): except Organization.custom_label.RelatedObjectDoesNotExist: return self.labels.first() - def __str__(self): - elements = [ - str(self.name) if self.name else '', - str(self.location) if self.location else '', - ] - return ', '.join([element for element in elements if element]) - @property def location(self): """ @@ -2032,6 +2048,26 @@ def location(self): """ return self.locations.first() if self.locations else None + @property + def names(self): + """ + All names. + """ + return OrganizationName.objects.filter( + models.Q(ror_display_for=self) | + models.Q(custom_label_for=self) | + models.Q(label_for=self) | + models.Q(alias_for=self) | + models.Q(acronym_for=self), + ) + + @property + def also_known_as(self): + """ + All names excluding the ROR display name. + """ + return self.names.exclude(ror_display_for=self) + @classmethod def naive_get_or_create( cls, @@ -2094,9 +2130,13 @@ def create_from_ror_record(cls, record): organization, created = cls.objects.get_or_create( ror=record.get('id', ''), ) - if record.get('status'): - organization.ror_status = record.get('status') + organization.ror_status = record.get('status', cls.RORStatus.UNKNOWN) + for link in record.get('links', []): + if link.get('type') == 'website': + organization.website = link.get('value', '') + break organization.save() + for name in record.get('names'): kwargs = {} kwargs['value'] = name.get('value', '') @@ -2111,6 +2151,7 @@ def create_from_ror_record(cls, record): if 'acronym' in name.get('types'): kwargs['acronym_for'] = organization OrganizationName.objects.get_or_create(**kwargs) + for location in record.get('locations'): details = location.get('geonames_details', {}) country, created = Country.objects.get_or_create( @@ -2198,19 +2239,30 @@ class Affiliation(models.Model): ) is_primary = models.BooleanField( default=False, + help_text="Each account can have one primary affiliation", ) start = models.DateField( blank=True, null=True, + verbose_name="Start date", ) end = models.DateField( blank=True, null=True, + verbose_name="End date", + help_text="Leave empty for a current affiliation", ) class Meta: ordering = ['-pk'] + def title_department(self): + elements = [ + self.title, + self.department, + ] + return ', '.join([element for element in elements if element]) + def __str__(self): elements = [ self.title, diff --git a/src/repository/migrations/0044_remove_preprintauthor_affiliation_and_more.py b/src/repository/migrations/0044_remove_preprintauthor_affiliation_and_more.py index 194f2f4a7d..2391580b45 100644 --- a/src/repository/migrations/0044_remove_preprintauthor_affiliation_and_more.py +++ b/src/repository/migrations/0044_remove_preprintauthor_affiliation_and_more.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('repository', '0043_auto_20240402_1256'), + ('repository', '0045_historicalrepository_display_public_metrics_and_more'), ] operations = [ diff --git a/src/submission/migrations/0080_remove_frozenauthor_country_and_more.py b/src/submission/migrations/0080_remove_frozenauthor_country_and_more.py index 02968fbbcb..0c5ccc0f99 100644 --- a/src/submission/migrations/0080_remove_frozenauthor_country_and_more.py +++ b/src/submission/migrations/0080_remove_frozenauthor_country_and_more.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('submission', '0079_merge_20240602_1739'), + ('submission', '0084_remove_article_jats_article_type_and_more'), ] operations = [ diff --git a/src/utils/migrations/0035_rorimport_rorimporterror.py b/src/utils/migrations/0035_rorimport_rorimporterror.py index a22c56b89c..a4e9a2c86d 100644 --- a/src/utils/migrations/0035_rorimport_rorimporterror.py +++ b/src/utils/migrations/0035_rorimport_rorimporterror.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('utils', '0034_rename_toaddress_addressee'), + ('utils', '0038_upgrade_1_7_2'), ] operations = [ diff --git a/src/utils/models.py b/src/utils/models.py index d4481c30d6..4b14ab1ed8 100755 --- a/src/utils/models.py +++ b/src/utils/models.py @@ -466,12 +466,12 @@ def get_records(self): def delete_previous_download(self): if not self.previous_import: - logger.info('No previous import to remove.') + logger.debug('No previous import to remove.') return try: os.unlink(self.previous_import.zip_path) except FileNotFoundError: - logger.info('Previous import had no zip file.') + logger.debug('Previous import had no zip file.') def download_data(self): """