From 8281195fd4ec3c3e1dcfb0fdcad1a7f1e8c1ff54 Mon Sep 17 00:00:00 2001 From: Paul Tomasula Date: Mon, 4 Mar 2024 17:05:19 -0500 Subject: [PATCH 1/6] Fix 500 error on streamwatch Related issue #707 It looks like the ownership model was never changed from individual accounts to organizations for this view. --- src/streamwatch/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/streamwatch/views.py b/src/streamwatch/views.py index 44077ca5..94339b82 100644 --- a/src/streamwatch/views.py +++ b/src/streamwatch/views.py @@ -183,7 +183,7 @@ def get_context_data(self, **kwargs): ] = user.is_authenticated and user.can_administer_site( registration.sampling_feature_id ) - context["is_site_owner"] = user.id == registration.django_user + context["is_site_owner"] = registration.organization_id in user.organization_id context["sampling_feature_code"] = self.kwargs[self.slug_field] context["action_id"] = int(self.kwargs["pk"]) return context From 74539443786ed9bf4b34190b017f76815fcba233 Mon Sep 17 00:00:00 2001 From: Paul Tomasula Date: Tue, 5 Mar 2024 14:32:06 -0500 Subject: [PATCH 2/6] Fix user cannot register site Related issue #709 Users are supposed to have a dropdown of organizations they are affiliated with appears on this form. It looks like the template had a user is staff requirement, which prevented them from seeing and populating this required field. --- .../templates/includes/site-form-fieldset.template.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dataloaderinterface/templates/includes/site-form-fieldset.template.html b/src/dataloaderinterface/templates/includes/site-form-fieldset.template.html index c98afad9..c7a94925 100644 --- a/src/dataloaderinterface/templates/includes/site-form-fieldset.template.html +++ b/src/dataloaderinterface/templates/includes/site-form-fieldset.template.html @@ -4,7 +4,6 @@
- {% if user.is_staff %}
- {% endif %}
From 73de841646dc6753ec26e53e6fcb34354520258a Mon Sep 17 00:00:00 2001 From: Paul Tomasula Date: Tue, 5 Mar 2024 15:19:22 -0500 Subject: [PATCH 3/6] Move model base to separate file --- src/odm2/models/__init__.py | 1 + src/odm2/models/base.py | 3 +++ src/odm2/models/results.py | 6 +----- 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 src/odm2/models/base.py diff --git a/src/odm2/models/__init__.py b/src/odm2/models/__init__.py index e69de29b..f27ebe41 100644 --- a/src/odm2/models/__init__.py +++ b/src/odm2/models/__init__.py @@ -0,0 +1 @@ +import odm2.models.base \ No newline at end of file diff --git a/src/odm2/models/base.py b/src/odm2/models/base.py new file mode 100644 index 00000000..5bbe43ea --- /dev/null +++ b/src/odm2/models/base.py @@ -0,0 +1,3 @@ +from sqlalchemy.orm import declarative_base + +Base = declarative_base() diff --git a/src/odm2/models/results.py b/src/odm2/models/results.py index 56ab59f0..8b488fca 100644 --- a/src/odm2/models/results.py +++ b/src/odm2/models/results.py @@ -4,10 +4,7 @@ from sqlalchemy import orm import sqlalchemy as sqla from sqlalchemy.dialects import postgresql as pg -from sqlalchemy.orm import declarative_base - -Base = declarative_base() - +from odm2.models.base import Base class TimeSeriesResults(Base): """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TimeSeriesResults.html""" @@ -47,7 +44,6 @@ class TimeSeriesResults(Base): "aggregationstaticcv", sqla.ForeignKey("cv_aggregationstatistic.term") ) - class TimeSeriesResultValues(Base): """http://odm2.github.io/ODM2/schemas/ODM2_Current/tables/ODM2Results_TimeSeriesResultValues.html""" From 2f3409689ecbf7a1a468dde3bd91d8fdf9a5be37 Mon Sep 17 00:00:00 2001 From: Paul Tomasula Date: Tue, 5 Mar 2024 16:23:03 -0500 Subject: [PATCH 4/6] Seed organization for new users Related issue #708 --- src/accounts/user.py | 604 +++++++++--------- src/odm2/crud/__init__.py | 2 + .../{organizations.py => organization.py} | 28 + 3 files changed, 331 insertions(+), 303 deletions(-) create mode 100644 src/odm2/crud/__init__.py rename src/odm2/crud/{organizations.py => organization.py} (60%) diff --git a/src/accounts/user.py b/src/accounts/user.py index cb975483..2c8183dc 100644 --- a/src/accounts/user.py +++ b/src/accounts/user.py @@ -1,303 +1,301 @@ -from collections.abc import Mapping -from typing import Any, Union, Dict, List -import datetime - -from sqlalchemy.orm import Query - -from accounts.base_user import User -import dataloader -from accounts.cognito_updater import CognitoUpdater -import odm2 -from odm2 import odm2datamodels - -models = odm2datamodels.models -odm2_engine = odm2datamodels.odm2_engine - - -class ODM2User(User): - __cognito_updater = CognitoUpdater() - - @classmethod - def from_mapping(cls, mapping: Mapping[str, Any]) -> "User": - """Converts ODM2 Accounts table GET request response into a User object""" - instance = cls() - - # define ODM2User properties using the mapping - instance.__user_id = int(mapping["accountid"]) - instance.__cognitoid = str(mapping["cognitoid"]) - instance.__username = str(mapping["username"]) - instance.__isactive = bool(mapping["active"]) - instance.__email = str(mapping["accountemail"]) - instance.__first_name = str(mapping["accountfirstname"]) - instance.__last_name = str(mapping["accountlastname"]) - instance.__is_admin = bool(mapping["issiteadmin"]) - return instance - - @classmethod - def create_new_user(cls, mapping: Mapping[str, Any]) -> "User": - """Creates a new user in the ODM2 Accounts table using Amazon Cognito fields as the mapping""" - # If there is a legacy_id in cognito this means that there is an existing account - # which will have been carried over to the account table. This means we just need - # to update the record - if "custom:legacy_id" in mapping: - user = odm2_engine.read_object(models.Accounts, mapping["custom:legacy_id"]) - user["cognitoid"] = mapping["sub"] - odm2_engine.update_object(models.Accounts, user["accountid"], user) - return cls.from_userid(user["accountid"]) - - user = models.Accounts() - user.cognitoid = mapping["sub"] - user.username = mapping["preferred_username"] - user.accountfirstname = mapping["given_name"] - user.accountlastname = mapping["family_name"] - user.accountemail = mapping["email"] - user.active = True - user.issiteadmin = False - pkey = odm2_engine.create_object(user) - - # create affiliation record - affiliation = models.Affiliations() - affiliation.affiliationstartdate = datetime.datetime.now() - affiliation.primaryemail = mapping["email"] - affiliation.accountid = pkey - odm2_engine.create_object(affiliation) - - return cls.from_userid(userid=pkey) - - @classmethod - def from_cognitoid(cls, cognitoid: str) -> Union["ODM2User", None]: - # take cognitoid (username field) and query for account record in the ODM2 database - accounts = models.Accounts - query = Query(accounts).filter(accounts.cognitoid == cognitoid) - try: - user_dict = odm2_engine.read_query( - query, output_format="dict", orient="records" - ) - except odm2.exceptions.ObjectNotFound as e: - return None - - # if the account exists, instantiate an ODM2User object - if len(user_dict) > 0: - return cls.from_mapping(user_dict[0]) - return None - - @classmethod - def from_userid(cls, userid: Union[int, str]) -> Union["User", None]: - """Takes a userid (accountid in ODM2) and queries for user record then returns a User instance""" - # takes pkey for account record query accounts database table - accounts_model = models.Accounts - try: - user_dict = odm2_engine.read_object(accounts_model, userid) - except odm2.exceptions.ObjectNotFound as e: - return None - - # if user does not exist in the database, return none - if len(user_dict) == 0: - return None - - # otherwise, instantiate the user object from the dictionary as the mapping - else: - return cls.from_mapping(user_dict) - - def _set_access_token(self, token: str) -> None: - """Protected method for use on backend. Sets Cognito access token""" - self.__access_token = token - - def _get_access_token(self) -> str: - return self.__access_token - - def __update_database_record(self, field_name: str, value: Any) -> None: - odm2_engine.update_object( - model=models.Accounts, - pkey=self.user_id, - data={field_name: value}, - ) - - def __update_cognito_record( - self, attribute_name: str, attribute_value: str - ) -> None: - self.__cognito_updater.update_user_attribute( - self, attribute_name, attribute_value - ) - - # PRT - TODO: The setters should also commit changes to the database and probably - # back to cognito. Makes me wonder if we even need settings in this context. - # should evaluate this need and finish implementation or remove setting support. - - # primary key for postgres database. Keep as ReadOnly. - @property - def user_id(self): - return self.__user_id - - # primary key for cognito. Keep as ReadOnly. - @property - def congitoid(self): - return self.__congitoid - - @property - def username(self) -> str: - return self.__username - - @username.setter - def username(self, value: str) -> None: - self.__update_database_record("username", value) - self.__username = value - self.__update_cognito_record("preferred_username", value) - - @property - def is_active(self): - return self.__isactive - - @is_active.setter - def isactive(self, value: bool): - if isinstance(value, bool): - self._isactive = value - - @property - def email(self) -> str: - return self.__email - - @email.setter - def email(self, value: str) -> None: - self.__update_database_record("accountemail", value) - self.__email = value - self.__update_cognito_record("email", value) - - @property - def first_name(self) -> str: - return self.__first_name - - @first_name.setter - def first_name(self, value: str) -> None: - self.__update_database_record("accountfirstname", value) - self.__first_name = value - self.__update_cognito_record("given_name", value) - - @property - def last_name(self) -> str: - return self.__last_name - - @last_name.setter - def last_name(self, value: str) -> None: - self.__update_database_record("accountlastname", value) - self.__last_name = value - self.__update_cognito_record("family_name", value) - - @property - def is_authenticated(self): - """Returns if the user is authenticated, Always False from for AnonymousUsers""" - # should return True by default, but could also use is active field? - return True - - def has_permission(self, permissions: str) -> bool: - """Check if user has permission based on applications permissions implementation""" - # return false for now - # need to further think through permissions implementation - return False - - def owns_site(self, sampling_feature_id: int) -> bool: - """Given a dataloader Registration instance, checks if the registration belows to the user""" - # TODO - query = "SELECT * FROM dataloaderinterface_siteregistration WHERE \"SamplingFeatureID\" = '%s';" - - #presently being affiliated with site grants ownership, though this will update with permissions later - - organization_id = None - with odm2_engine.engine.connect() as connection: - site_registration = connection.execute( - query, (sampling_feature_id) - ).fetchall() - organization_id= site_registration[0]["OrganizationID"] - - for a in self.affiliation: - if a.organization_id == organization_id: return True - return False - - def can_administer_site(self, sampling_feature_id: int) -> bool: - """Given a dataloader Registration instance, checks if the user is able to administer the registration""" - return self.is_staff or self.owns_site(sampling_feature_id) - - def _get_affiliation(self) -> Union[None, Dict]: - query = Query(models.Affiliations).filter( - models.Affiliations.accountid == self.user_id - ) - try: - affiliations = odm2_engine.read_query( - query, output_format="dict", orient="records" - ) - return affiliations - except odm2.exceptions.ObjectNotFound as e: - return None - except IndexError as e: - return None - - def _get_organization(self) -> List[Dict]: - organization_ids = [a.organization_id for a in self.affiliation] - query = Query(models.Organizations).filter( - models.Organizations.organizationid.in_(organization_ids) - ) - try: - organizations = odm2_engine.read_query( - query, output_format="dict", orient="records" - ) - return organizations - except odm2.exceptions.ObjectNotFound as e: - return None - except IndexError as e: - return None - - @property - def affiliation_id(self) -> Union[List[int], None]: - affiliation = self._get_affiliation() - if not affiliation: - return None - return [a['affiliationid'] for a in affiliation] - - @property - def organization_code(self) -> List[str]: - organizations = self._get_organization() - if organizations is None: - return [] - return [o["organizationcode"] for o in organizations] - - @property - def organization_name(self) -> List[str]: - organizations = self._get_organization() - if organizations is None: - return [] - return [o["organizationname"] for o in organizations] - - @property - def organization_id(self) -> List[int]: - organizations = self._get_organization() - if organizations is None: - return [] - return [o["organizationid"] for o in organizations] - - @organization_id.setter - def organization_id(self, value: int) -> None: - # TODO: verify that all users get a base affiliation record on account creation - affiliation_id = self.affiliation_id - odm2_engine.update_object( - models.Affiliations, affiliation_id, {"organizationid": value} - ) - - @property - def affiliation(self) -> List["Affiliation"]: - if not self.affiliation_id: - return [] - return list(dataloader.models.Affiliation.objects.filter(pk__in=self.affiliation_id).all()) - - @property - def is_staff(self) -> bool: - return self.__is_admin - - def has_perm(self, perm, obj=None): - return self.__is_admin - - def has_module_perms(self, app_label): - return self.__is_admin - - @property - def pk(self): - return self.id +from collections.abc import Mapping +from typing import Any, Union, Dict, List +import datetime + +from sqlalchemy.orm import Query + +from accounts.base_user import User +import dataloader +from accounts.cognito_updater import CognitoUpdater +import odm2 +from odm2 import odm2datamodels +from odm2 import crud + +models = odm2datamodels.models +odm2_engine = odm2datamodels.odm2_engine + + +class ODM2User(User): + __cognito_updater = CognitoUpdater() + + @classmethod + def from_mapping(cls, mapping: Mapping[str, Any]) -> "User": + """Converts ODM2 Accounts table GET request response into a User object""" + instance = cls() + + # define ODM2User properties using the mapping + instance.__user_id = int(mapping["accountid"]) + instance.__cognitoid = str(mapping["cognitoid"]) + instance.__username = str(mapping["username"]) + instance.__isactive = bool(mapping["active"]) + instance.__email = str(mapping["accountemail"]) + instance.__first_name = str(mapping["accountfirstname"]) + instance.__last_name = str(mapping["accountlastname"]) + instance.__is_admin = bool(mapping["issiteadmin"]) + return instance + + @classmethod + def create_new_user(cls, mapping: Mapping[str, Any]) -> "User": + """Creates a new user in the ODM2 Accounts table using Amazon Cognito fields as the mapping""" + # If there is a legacy_id in cognito this means that there is an existing account + # which will have been carried over to the account table. This means we just need + # to update the record + if "custom:legacy_id" in mapping: + user = odm2_engine.read_object(models.Accounts, mapping["custom:legacy_id"]) + user["cognitoid"] = mapping["sub"] + odm2_engine.update_object(models.Accounts, user["accountid"], user) + return cls.from_userid(user["accountid"]) + + #create the account record for the user + user = models.Accounts() + user.cognitoid = mapping["sub"] + user.username = mapping["preferred_username"] + user.accountfirstname = mapping["given_name"] + user.accountlastname = mapping["family_name"] + user.accountemail = mapping["email"] + user.active = True + user.issiteadmin = False + pkey = odm2_engine.create_object(user) + + #create organization and affiliation + crud.organization.create_individual_organization(account=user) + + return cls.from_userid(userid=pkey) + + @classmethod + def from_cognitoid(cls, cognitoid: str) -> Union["ODM2User", None]: + # take cognitoid (username field) and query for account record in the ODM2 database + accounts = models.Accounts + query = Query(accounts).filter(accounts.cognitoid == cognitoid) + try: + user_dict = odm2_engine.read_query( + query, output_format="dict", orient="records" + ) + except odm2.exceptions.ObjectNotFound as e: + return None + + # if the account exists, instantiate an ODM2User object + if len(user_dict) > 0: + return cls.from_mapping(user_dict[0]) + return None + + @classmethod + def from_userid(cls, userid: Union[int, str]) -> Union["User", None]: + """Takes a userid (accountid in ODM2) and queries for user record then returns a User instance""" + # takes pkey for account record query accounts database table + accounts_model = models.Accounts + try: + user_dict = odm2_engine.read_object(accounts_model, userid) + except odm2.exceptions.ObjectNotFound as e: + return None + + # if user does not exist in the database, return none + if len(user_dict) == 0: + return None + + # otherwise, instantiate the user object from the dictionary as the mapping + else: + return cls.from_mapping(user_dict) + + def _set_access_token(self, token: str) -> None: + """Protected method for use on backend. Sets Cognito access token""" + self.__access_token = token + + def _get_access_token(self) -> str: + return self.__access_token + + def __update_database_record(self, field_name: str, value: Any) -> None: + odm2_engine.update_object( + model=models.Accounts, + pkey=self.user_id, + data={field_name: value}, + ) + + def __update_cognito_record( + self, attribute_name: str, attribute_value: str + ) -> None: + self.__cognito_updater.update_user_attribute( + self, attribute_name, attribute_value + ) + + # PRT - TODO: The setters should also commit changes to the database and probably + # back to cognito. Makes me wonder if we even need settings in this context. + # should evaluate this need and finish implementation or remove setting support. + + # primary key for postgres database. Keep as ReadOnly. + @property + def user_id(self): + return self.__user_id + + # primary key for cognito. Keep as ReadOnly. + @property + def congitoid(self): + return self.__congitoid + + @property + def username(self) -> str: + return self.__username + + @username.setter + def username(self, value: str) -> None: + self.__update_database_record("username", value) + self.__username = value + self.__update_cognito_record("preferred_username", value) + + @property + def is_active(self): + return self.__isactive + + @is_active.setter + def isactive(self, value: bool): + if isinstance(value, bool): + self._isactive = value + + @property + def email(self) -> str: + return self.__email + + @email.setter + def email(self, value: str) -> None: + self.__update_database_record("accountemail", value) + self.__email = value + self.__update_cognito_record("email", value) + + @property + def first_name(self) -> str: + return self.__first_name + + @first_name.setter + def first_name(self, value: str) -> None: + self.__update_database_record("accountfirstname", value) + self.__first_name = value + self.__update_cognito_record("given_name", value) + + @property + def last_name(self) -> str: + return self.__last_name + + @last_name.setter + def last_name(self, value: str) -> None: + self.__update_database_record("accountlastname", value) + self.__last_name = value + self.__update_cognito_record("family_name", value) + + @property + def is_authenticated(self): + """Returns if the user is authenticated, Always False from for AnonymousUsers""" + # should return True by default, but could also use is active field? + return True + + def has_permission(self, permissions: str) -> bool: + """Check if user has permission based on applications permissions implementation""" + # return false for now + # need to further think through permissions implementation + return False + + def owns_site(self, sampling_feature_id: int) -> bool: + """Given a dataloader Registration instance, checks if the registration belows to the user""" + # TODO + query = "SELECT * FROM dataloaderinterface_siteregistration WHERE \"SamplingFeatureID\" = '%s';" + + #presently being affiliated with site grants ownership, though this will update with permissions later + + organization_id = None + with odm2_engine.engine.connect() as connection: + site_registration = connection.execute( + query, (sampling_feature_id) + ).fetchall() + organization_id= site_registration[0]["OrganizationID"] + + for a in self.affiliation: + if a.organization_id == organization_id: return True + return False + + def can_administer_site(self, sampling_feature_id: int) -> bool: + """Given a dataloader Registration instance, checks if the user is able to administer the registration""" + return self.is_staff or self.owns_site(sampling_feature_id) + + def _get_affiliation(self) -> Union[None, Dict]: + query = Query(models.Affiliations).filter( + models.Affiliations.accountid == self.user_id + ) + try: + affiliations = odm2_engine.read_query( + query, output_format="dict", orient="records" + ) + return affiliations + except odm2.exceptions.ObjectNotFound as e: + return None + except IndexError as e: + return None + + def _get_organization(self) -> List[Dict]: + organization_ids = [a.organization_id for a in self.affiliation] + query = Query(models.Organizations).filter( + models.Organizations.organizationid.in_(organization_ids) + ) + try: + organizations = odm2_engine.read_query( + query, output_format="dict", orient="records" + ) + return organizations + except odm2.exceptions.ObjectNotFound as e: + return None + except IndexError as e: + return None + + @property + def affiliation_id(self) -> Union[List[int], None]: + affiliation = self._get_affiliation() + if not affiliation: + return None + return [a['affiliationid'] for a in affiliation] + + @property + def organization_code(self) -> List[str]: + organizations = self._get_organization() + if organizations is None: + return [] + return [o["organizationcode"] for o in organizations] + + @property + def organization_name(self) -> List[str]: + organizations = self._get_organization() + if organizations is None: + return [] + return [o["organizationname"] for o in organizations] + + @property + def organization_id(self) -> List[int]: + organizations = self._get_organization() + if organizations is None: + return [] + return [o["organizationid"] for o in organizations] + + @organization_id.setter + def organization_id(self, value: int) -> None: + # TODO: verify that all users get a base affiliation record on account creation + affiliation_id = self.affiliation_id + odm2_engine.update_object( + models.Affiliations, affiliation_id, {"organizationid": value} + ) + + @property + def affiliation(self) -> List["Affiliation"]: + if not self.affiliation_id: + return [] + return list(dataloader.models.Affiliation.objects.filter(pk__in=self.affiliation_id).all()) + + @property + def is_staff(self) -> bool: + return self.__is_admin + + def has_perm(self, perm, obj=None): + return self.__is_admin + + def has_module_perms(self, app_label): + return self.__is_admin + + @property + def pk(self): + return self.id diff --git a/src/odm2/crud/__init__.py b/src/odm2/crud/__init__.py new file mode 100644 index 00000000..4064d79a --- /dev/null +++ b/src/odm2/crud/__init__.py @@ -0,0 +1,2 @@ +import odm2.crud.organization +import odm2.crud.users \ No newline at end of file diff --git a/src/odm2/crud/organizations.py b/src/odm2/crud/organization.py similarity index 60% rename from src/odm2/crud/organizations.py rename to src/odm2/crud/organization.py index ae4a1b07..2ba7f282 100644 --- a/src/odm2/crud/organizations.py +++ b/src/odm2/crud/organization.py @@ -1,3 +1,5 @@ +from datetime import datetime + from typing import List, Optional, Tuple from sqlalchemy.orm import Session @@ -6,6 +8,32 @@ models = odm2datamodels.models session = odm2datamodels.odm2_engine.session_maker +def create_individual_organization( + account: models.Accounts, + session: Session = session(), +) -> None: + #create organization model + organization = models.Organizations() + organization.organizationtypecv = 'Individual' + organization.organizationcode = f'individual_org_account_{account.accountid}' + organization.organizationname = f'individual_org_account_{account.accountid}' + + #create organization record + session.add(organization) + session.commit() + + #make affiliation record to tie new organization to account + affiliation = models.Affiliations() + affiliation.organizationid=organization.organizationid + affiliation.isprimaryorganizationcontact=True + affiliation.affiliationstartdate = datetime.today() + affiliation.primaryemail = account.accountemail + affiliation.accountid = account.accountid + + #add record to database + session.add(affiliation) + session.commit() + def read_organization_names( session: Session = session(), organization_ids:Optional[List[int]]=None, From 7289504ffdcd1ec162de7e61666010acfc1aa1ad Mon Sep 17 00:00:00 2001 From: Paul Tomasula Date: Wed, 6 Mar 2024 09:35:33 -0500 Subject: [PATCH 5/6] Fix 500 error on edit page Related issue #712 Fixes broken import. --- src/dataloaderinterface/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dataloaderinterface/views.py b/src/dataloaderinterface/views.py index 5345fe9f..ce6d2be3 100644 --- a/src/dataloaderinterface/views.py +++ b/src/dataloaderinterface/views.py @@ -40,6 +40,8 @@ from odm2 import odm2datamodels from sqlalchemy import text +from odm2.crud.organization import read_organization_names +from odm2 import create_session class LoginRequiredMixin(object): @classmethod @@ -409,12 +411,10 @@ def get_form(self, form_class=None): user = self.request.user organization_ids = [a.organization.organization_id for a in user.affiliation] #for staff/admins if users is site admin they should see all organizations - if not user.is_staff: + if user.is_staff: organization_ids = None choices = [] - from odm2.crud.organizations import read_organization_names - from odm2 import create_session session = create_session() organizations = read_organization_names(session, organization_ids) From eaeeadaa7e4f5751f8cac108bac7e2d5d9d3c8a0 Mon Sep 17 00:00:00 2001 From: Paul Tomasula Date: Tue, 19 Mar 2024 10:11:08 -0400 Subject: [PATCH 6/6] Restore CSV Download Related Issue #713 The CSV download view was still pulling affiliation information based on a site registrations account. With the change to organizational ownership, a SiteRegistration's affiliation is not tied to a single account but multiple accounts, and the relationship is no longer 1-to-1 (i.e. site's are affiliated with multiple accounts). For those reasons I removed the affiliation related portions of the metadata causing the error but to a bad (stale) query strucutre. --- .../csv_templates/source_info_template.txt | 15 ++++++--------- src/dataloaderservices/views.py | 7 ++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/dataloaderservices/csv_templates/source_info_template.txt b/src/dataloaderservices/csv_templates/source_info_template.txt index 48be81f4..41dcf142 100644 --- a/src/dataloaderservices/csv_templates/source_info_template.txt +++ b/src/dataloaderservices/csv_templates/source_info_template.txt @@ -1,9 +1,6 @@ -# Source Information -# ------------------ -# Organization: {affiliation.organization} -# SourceLink: {source_link} -# ContactName: {affiliation.account_id.accountfirstname} {affiliation.account_id.accountlastname} -# Phone: {affiliation.primary_phone} -# Email: {affiliation.primary_email} -# Citation: {citation} -# +# Source Information +# ------------------ +# Organization: {organization.display_name} +# SourceLink: {source_link} +# Citation: {citation} +# diff --git a/src/dataloaderservices/views.py b/src/dataloaderservices/views.py index 5307a8b6..cf1bb181 100644 --- a/src/dataloaderservices/views.py +++ b/src/dataloaderservices/views.py @@ -95,7 +95,7 @@ def post(self, request, format=None): height = form.cleaned_data["height"] notes = form.cleaned_data["sensor_notes"] - #create action-by record + # create action-by record affiliation = None for a in self.request.user.affiliation: if a.organization_id == registration.organization_id: @@ -682,7 +682,8 @@ def generate_metadata( # Write Source Information data # affiliation = tsr.result.feature_action.action.people.first() - affiliation = site_sensor.registration.odm2_affiliation + # affiliation = site_sensor.registration.odm2_affiliation + organization = site_sensor.registration.organization annotation = tsr.result.annotations.first() citation = ( annotation.citation.title if annotation and annotation.citation else "" @@ -706,7 +707,7 @@ def generate_metadata( ) metadata += CSVDataApi.read_file("source_info_template.txt").format( - affiliation=affiliation, citation=citation, source_link=source_link + organization=organization, citation=citation, source_link=source_link ) return metadata