Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot save Collecting Trip or Storage with an attachment #3289

Closed
grantfitzsimmons opened this issue Apr 3, 2023 · 14 comments · May be fixed by #3315
Closed

Cannot save Collecting Trip or Storage with an attachment #3289

grantfitzsimmons opened this issue Apr 3, 2023 · 14 comments · May be fixed by #3315
Assignees
Labels
1 - Bug Incorrect behavior of the product

Comments

@grantfitzsimmons
Copy link
Member

https://fwri-edge.test.specifysystems.org/specify/view/collectingtrip/991/

After uploading an attachment, you are unable to save without a crash.

Short error:

TypeError at /api/specify/collectingtrip/991/ cannot unpack non-iterable NoneType object 

Full error:

TypeError at /api/specify/collectingtrip/991/ cannot unpack non-iterable NoneType object Request Method: PUT Request URL: http://fwri-edge.test.specifysystems.org/api/specify/collectingtrip/991/ Django Version: 3.2.15 Python Executable: /opt/specify7/ve/bin/python3.8 Python Version: 3.8.0 Python Path: ['/opt/specify7', '/opt/specify7', '/opt/specify7/ve/bin', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/opt/specify7/ve/lib/python3.8/site-packages', '/opt/specify7'] Server time: Mon, 03 Apr 2023 18:45:01 -0500 Installed Applications: ('django.contrib.sessions', 'django.contrib.staticfiles', 'django.contrib.contenttypes', 'django.contrib.auth', 'specifyweb.specify', 'specifyweb.permissions', 'specifyweb.accounts', 'specifyweb.stored_queries', 'specifyweb.businessrules', 'specifyweb.express_search', 'specifyweb.context', 'specifyweb.attachment_gw', 'specifyweb.frontend', 'specifyweb.barvis', 'specifyweb.report_runner', 'specifyweb.interactions', 'specifyweb.workbench', 'specifyweb.notifications', 'specifyweb.export', 'specifyweb.raven_placeholder') Installed Middleware: ['django.middleware.gzip.GZipMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'specifyweb.context.middleware.ContextMiddleware', 'specifyweb.permissions.middleware.PermissionsMiddleware', 'specifyweb.middleware.general.GeneralMiddleware'] Traceback (most recent call last): File "/opt/specify7/ve/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/opt/specify7/ve/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/opt/specify7/specifyweb/specify/views.py", line 29, in wrapped return view(request, *args, **kwargs) File "/opt/specify7/ve/lib/python3.8/site-packages/django/views/decorators/cache.py", line 31, in _cache_controlled response = viewfunc(request, *args, **kw) File "/opt/specify7/specifyweb/specify/views.py", line 62, in view return dispatch_func(request, *args, **kwargs) File "/opt/specify7/specifyweb/specify/api.py", line 132, in resource_dispatch obj = put_resource(request.specify_collection, File "/usr/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "/opt/specify7/specifyweb/specify/api.py", line 612, in put_resource return update_obj(collection, agent, name, id, version, data) File "/opt/specify7/specifyweb/specify/api.py", line 639, in update_obj handle_to_many(collection, agent, obj, data) File "/opt/specify7/specifyweb/specify/api.py", line 571, in handle_to_many rel_obj = create_obj(collection, agent, rel_model, rel_data, parent_obj=obj) File "/opt/specify7/specifyweb/specify/api.py", line 386, in create_obj autonumber_and_save(collection, agent.specifyuser, obj) File "/opt/specify7/specifyweb/specify/autonumbering.py", line 28, in autonumber_and_save obj.save() File "/opt/specify7/specifyweb/specify/build_models.py", line 63, in save return super(model, self).save(*args, **kwargs) File "/opt/specify7/ve/lib/python3.8/site-packages/django/db/models/base.py", line 739, in save self.save_base(using=using, force_insert=force_insert, File "/opt/specify7/ve/lib/python3.8/site-packages/django/db/models/base.py", line 763, in save_base pre_save.send( File "/opt/specify7/ve/lib/python3.8/site-packages/django/dispatch/dispatcher.py", line 180, in send return [ File "/opt/specify7/ve/lib/python3.8/site-packages/django/dispatch/dispatcher.py", line 181, in (receiver, receiver(signal=self, sender=sender, **named)) File "/opt/specify7/specifyweb/businessrules/orm_signal_handler.py", line 19, in handler rule(sender, kwargs['instance']) File "/opt/specify7/specifyweb/businessrules/attachment_rules.py", line 25, in attachment_jointable_save obj.attachment.scopetype, obj.attachment.scopeid = Scoping(attachee)() Exception Type: TypeError at /api/specify/collectingtrip/991/ Exception Value: cannot unpack non-iterable NoneType object Request information: USER: Specifyuser object (1) GET: No GET data POST: No POST data FILES: No FILES data COOKIES: csrftoken = 'eLhqRqBmQAxdhMNfJuz3UWT1tMMFmmb5DNmjE5oOcgV2lMWqaBva34zPzl5Qxe2K' sessionid = '39wft9ktzpnkushd23gvgfe8xmeqmchw' collection = '4' META: CONTENT_LENGTH = '1239' CONTENT_TYPE = 'application/json' CSRF_COOKIE = 'eLhqRqBmQAxdhMNfJuz3UWT1tMMFmmb5DNmjE5oOcgV2lMWqaBva34zPzl5Qxe2K' HTTP_ACCEPT = 'application/json' HTTP_ACCEPT_ENCODING = 'gzip, deflate, br' HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.5' HTTP_CONNECTION = 'close' HTTP_COOKIE = 'csrftoken=eLhqRqBmQAxdhMNfJuz3UWT1tMMFmmb5DNmjE5oOcgV2lMWqaBva34zPzl5Qxe2K; sessionid=39wft9ktzpnkushd23gvgfe8xmeqmchw; collection=4' HTTP_DNT = '1' HTTP_HOST = 'fwri-edge.test.specifysystems.org' HTTP_ORIGIN = 'https://fwri-edge.test.specifysystems.org' HTTP_REFERER = 'https://fwri-edge.test.specifysystems.org/specify/view/collectingtrip/991/' HTTP_SEC_FETCH_DEST = 'empty' HTTP_SEC_FETCH_MODE = 'cors' HTTP_SEC_FETCH_SITE = 'same-origin' HTTP_SEC_GPC = '1' HTTP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0' HTTP_X_CSRFTOKEN = '********************' HTTP_X_FORWARDED_FOR = '129.237.90.91' HTTP_X_REAL_IP = '129.237.90.91' PATH_INFO = '/api/specify/collectingtrip/991/' QUERY_STRING = '' RAW_URI = '/api/specify/collectingtrip/991/' REMOTE_ADDR = '172.28.0.22' REMOTE_PORT = '58304' REQUEST_METHOD = 'PUT' SCRIPT_NAME = '' SERVER_NAME = '0.0.0.0' SERVER_PORT = '8000' SERVER_PROTOCOL = 'HTTP/1.0' SERVER_SOFTWARE = 'gunicorn/20.1.0' gunicorn.socket = wsgi.errors = wsgi.file_wrapper = wsgi.input = wsgi.input_terminated = True wsgi.multiprocess = True wsgi.multithread = False wsgi.run_once = False wsgi.url_scheme = 'http' wsgi.version = '(1, 0)' Settings: Using settings module settings ABSOLUTE_URL_OVERRIDES = {} ADMINS = '()' ADMIN_MEDIA_PREFIX = '/static/admin/' ALLOWED_HOSTS = ['*'] ALLOW_SPECIFY6_PASSWORDS = '********************' ALLOW_SUPPORT_LOGIN = False ANONYMOUS_USER = None APPEND_SLASH = True AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend'] AUTH_LDAP_SERVER_URI = None AUTH_PASSWORD_VALIDATORS = '********************' AUTH_USER_MODEL = 'specify.Specifyuser' CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}} CACHE_MIDDLEWARE_ALIAS = 'default' CACHE_MIDDLEWARE_KEY_PREFIX = '********************' CACHE_MIDDLEWARE_SECONDS = 600 CELERY_BROKER_URL = 'redis://redis/0' CELERY_RESULT_BACKEND = 'redis://redis/1' CELERY_TASK_DEFAULT_QUEUE = 'fwri-edge' CSRF_COOKIE_AGE = 31449600 CSRF_COOKIE_DOMAIN = None CSRF_COOKIE_HTTPONLY = False CSRF_COOKIE_NAME = 'csrftoken' CSRF_COOKIE_PATH = '/' CSRF_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_SECURE = False CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN' CSRF_TRUSTED_ORIGINS = [] CSRF_USE_SESSIONS = False DATABASES = {'default': {'ENGINE': 'specifyweb.hibernateboolsbackend.backends.mysql', 'NAME': 'fwri', 'USER': 'root', 'PASSWORD': '********************', 'HOST': 'mariadb', 'PORT': '', 'OPTIONS': {}, 'TEST': {'CHARSET': None, 'COLLATION': None, 'MIGRATE': True, 'MIRROR': None, 'NAME': None}, 'ATOMIC_REQUESTS': False, 'AUTOCOMMIT': True, 'CONN_MAX_AGE': 0, 'TIME_ZONE': None}} DATABASE_HOST = 'mariadb' DATABASE_NAME = 'fwri' DATABASE_OPTIONS = {} DATABASE_PORT = '' DATABASE_ROUTERS = [] DATA_UPLOAD_MAX_MEMORY_SIZE = 419430400 DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000 DATETIME_FORMAT = 'N j, Y, P' DATETIME_INPUT_FORMATS = ['%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M', '%m/%d/%Y %H:%M:%S', '%m/%d/%Y %H:%M:%S.%f', '%m/%d/%Y %H:%M', '%m/%d/%y %H:%M:%S', '%m/%d/%y %H:%M:%S.%f', '%m/%d/%y %H:%M'] DATE_FORMAT = 'N j, Y' DATE_INPUT_FORMATS = ['%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%b %d %Y', '%b %d, %Y', '%d %b %Y', '%d %b, %Y', '%B %d %Y', '%B %d, %Y', '%d %B %Y', '%d %B, %Y'] DEBUG = True DEBUG_PROPAGATE_EXCEPTIONS = False DECIMAL_SEPARATOR = '.' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_CHARSET = 'utf-8' DEFAULT_EXCEPTION_REPORTER = 'django.views.debug.ExceptionReporter' DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFilter' DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' DEFAULT_FROM_EMAIL = 'webmaster@localhost' DEFAULT_HASHING_ALGORITHM = 'sha256' DEFAULT_INDEX_TABLESPACE = '' DEFAULT_TABLESPACE = '' DEPOSITORY_DIR = '/volumes/static-files/depository' DISABLE_AUDITING = False DISALLOWED_USER_AGENTS = [] EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'localhost' EMAIL_HOST_PASSWORD = '********************' EMAIL_HOST_USER = '' EMAIL_PORT = 25 EMAIL_SSL_CERTFILE = None EMAIL_SSL_KEYFILE = '********************' EMAIL_SUBJECT_PREFIX = '[Django] ' EMAIL_TIMEOUT = None EMAIL_USE_LOCALTIME = False EMAIL_USE_SSL = False EMAIL_USE_TLS = False FILE_UPLOAD_DIRECTORY_PERMISSIONS = None FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.MemoryFileUploadHandler', 'django.core.files.uploadhandler.TemporaryFileUploadHandler'] FILE_UPLOAD_MAX_MEMORY_SIZE = 104857600 FILE_UPLOAD_PERMISSIONS = 420 FILE_UPLOAD_TEMP_DIR = None FIRST_DAY_OF_WEEK = 0 FIXTURE_DIRS = [] FORCE_SCRIPT_NAME = None FORMAT_MODULE_PATH = None FORM_RENDERER = 'django.forms.renderers.DjangoTemplates' IGNORABLE_404_URLS = [] INSTALLED_APPS = "('django.contrib.sessions', 'django.contrib.staticfiles', 'django.contrib.contenttypes', 'django.contrib.auth', 'specifyweb.specify', 'specifyweb.permissions', 'specifyweb.accounts', 'specifyweb.stored_queries', 'specifyweb.businessrules', 'specifyweb.express_search', 'specifyweb.context', 'specifyweb.attachment_gw', 'specifyweb.frontend', 'specifyweb.barvis', 'specifyweb.report_runner', 'specifyweb.interactions', 'specifyweb.workbench', 'specifyweb.notifications', 'specifyweb.export', 'specifyweb.raven_placeholder')" INTERNAL_IPS = [] JAVA_PATH = '/usr/bin/java' LANGUAGES = [('en-us', 'English'), ('ru-ru', 'русский'), ('uk-ua', 'українська'), ('fr-fr', 'français'), ('es-es', 'español')] LANGUAGES_BIDI = ['he', 'ar', 'ar-dz', 'fa', 'ur'] LANGUAGE_CODE = 'en-us' LANGUAGE_COOKIE_AGE = None LANGUAGE_COOKIE_DOMAIN = None LANGUAGE_COOKIE_HTTPONLY = False LANGUAGE_COOKIE_NAME = 'language' LANGUAGE_COOKIE_PATH = '/' LANGUAGE_COOKIE_SAMESITE = None LANGUAGE_COOKIE_SECURE = False LOCALE_PATHS = "('/opt/specify7/frontend/locale',)" LOGGING = {'version': 1, 'disable_existing_loggers': False, 'formatters': {'standard': {'format': '[%(asctime)s] [%(levelname)s] [%(name)s:%(lineno)s] %(message)s', 'datefmt': '%d/%b/%Y %H:%M:%S'}}, 'handlers': {'console': {'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'standard'}}, 'loggers': {'django.request': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}, 'specifyweb': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False}}} LOGGING_CONFIG = 'logging.config.dictConfig' LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/accounts/login/' LOGOUT_REDIRECT_URL = None MANAGERS = '()' MASTER_NAME = 'root' MASTER_PASSWORD = '********************' MEDIA_ROOT = '' MEDIA_URL = '/' MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage' MIDDLEWARE = ['django.middleware.gzip.GZipMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'specifyweb.context.middleware.ContextMiddleware', 'specifyweb.permissions.middleware.PermissionsMiddleware', 'specifyweb.middleware.general.GeneralMiddleware'] MIGRATION_MODULES = {} MONTH_DAY_FORMAT = 'F j' NOTIFICATION_TTL_DAYS = 7 NUMBER_GROUPING = 0 OAUTH_LOGIN_PROVIDERS = {} PASSWORD_HASHERS = '********************' PASSWORD_RESET_TIMEOUT = '********************' PASSWORD_RESET_TIMEOUT_DAYS = '********************' PREPEND_WWW = False RAVEN_CONFIG = None REPORT_RUNNER_HOST = 'report-runner' REPORT_RUNNER_PORT = '8080' ROOT_URLCONF = 'specifyweb.urls' RO_MODE = False SA_DATABASE_URL = 'mysql://root:root@mariadb:3306/fwri?charset=utf8' SA_POOL_RECYCLE = 3600 SECRET_KEY = '********************' SECURE_BROWSER_XSS_FILTER = False SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_HSTS_INCLUDE_SUBDOMAINS = False SECURE_HSTS_PRELOAD = False SECURE_HSTS_SECONDS = 0 SECURE_PROXY_SSL_HEADER = None SECURE_REDIRECT_EXEMPT = [] SECURE_REFERRER_POLICY = 'same-origin' SECURE_SSL_HOST = None SECURE_SSL_REDIRECT = False SEPARATE_WEB_ATTACHMENT_FOLDERS = None SERVER_EMAIL = 'root@localhost' SESSION_CACHE_ALIAS = 'default' SESSION_COOKIE_AGE = 1209600 SESSION_COOKIE_DOMAIN = None SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_NAME = 'sessionid' SESSION_COOKIE_PATH = '/' SESSION_COOKIE_SAMESITE = 'Lax' SESSION_COOKIE_SECURE = False SESSION_ENGINE = 'django.contrib.sessions.backends.file' SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_FILE_PATH = None SESSION_SAVE_EVERY_REQUEST = False SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer' SETTINGS_MODULE = 'settings' SHORT_DATETIME_FORMAT = 'm/d/Y P' SHORT_DATE_FORMAT = 'm/d/Y' SIGNING_BACKEND = 'django.core.signing.TimestampSigner' SILENCED_SYSTEM_CHECKS = [] SITE_ID = 1 SPECIFY_CONFIG_DIR = '/opt/Specify/config' SPECIFY_THICK_CLIENT = '/opt/Specify' STATICFILES_DIRS = "(('config', '/opt/Specify/config'),)" STATICFILES_FINDERS = "('django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder')" STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' STATIC_ROOT = '' STATIC_URL = '/static/' STATS_URL = 'https://stats.specifycloud.org/capture' SUPPORT_LOGIN_TTL = 300 TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': {'context_processors': ['django.contrib.auth.context_processors.auth', 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.media', 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.request']}}] TEST_NON_SERIALIZED_APPS = [] TEST_RUNNER = 'django.test.runner.DiscoverRunner' THICK_CLIENT_LOCATION = '/opt/Specify' THOUSAND_SEPARATOR = ',' TIME_FORMAT = 'P' TIME_INPUT_FORMATS = ['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] TIME_ZONE = 'America/Chicago' USE_I18N = True USE_L10N = True USE_THOUSAND_SEPARATOR = False USE_TZ = False USE_X_FORWARDED_HOST = False USE_X_FORWARDED_PORT = False VERSION = 'edge(debug)' WB_UPLOAD_LOG_DIR = '/home/specify/wb_upload_logs' WEBPACK_LOADER = {'MANIFEST_FILE': '/static/manifest.json'} WEB_ATTACHMENT_COLLECTION = 'sp7demofish' WEB_ATTACHMENT_KEY = '********************' WEB_ATTACHMENT_REQUIRES_KEY_FOR_GET = '********************' WEB_ATTACHMENT_URL = 'https://demo-assets.specifycloud.org/web_asset_store.xml' WSGI_APPLICATION = None X_FRAME_OPTIONS = 'DENY' YEAR_MONTH_FORMAT = 'F Y' You’re seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard page generated by the handler for this status code. 

Specify 7 Crash Report - 2023-04-03T18 45 35.340Z.txt

Screen.Recording.2023-04-03.at.1.44.56.PM.mov

Reported By: @Plarson at the Speciforum

It seems like saving certain types of records after creating attachments for them frequently (or maybe always) results in a crash on save. I can’t list all of the tables where it happens off the top of my head, but it is happening consistently for me with the Collecting Trip table. I’m trying to attach a .jpg map but get the attached crash report when I save.
Specify 7 Crash Report - 2023-03-30T19_48_48.843Z.txt (801.4 KB)
This is not a problem, thankfully, for the collection object tables or the collecting information/event tables, but several others

@grantfitzsimmons grantfitzsimmons added 1 - Bug Incorrect behavior of the product pri:unknown labels Apr 3, 2023
@grantfitzsimmons
Copy link
Member Author

There are likely other tables with this issue. We need to go through each of the tables that have attachments and try it out.

  • Accession Attachment
  • Agent Attachment
  • Attachment
  • Attachment Metadata
  • Borrow Attachment
  • Collecting Event Attachment
  • Collecting Trip Attachment
  • Collection Object Attachment
  • Conservator Description Attachment
  • Conservator Event Attachment
  • DNA Sequence Attachment
  • DNA Sequencing Run Attachment
  • Deaccession Attachment
  • Disposal Attachment
  • Exchange In Attachment
  • Exchange Out Attachment
  • Field Notebook Attachment
  • Field Notebook Page Attachment
  • Field Notebook Page Set Attachment
  • Gift Attachment
  • Loan Attachment
  • Locality Attachment
  • Permit Attachment
  • Preparation Attachment
  • Reference Work Attachment
  • Repository Agreement Attachment
  • Storage Attachment
  • Taxon Attachment
  • Treatment Event Attachment

@specifysoftware
Copy link

This issue has been mentioned on Specify Community Forum. There might be relevant details there:

https://discourse.specifysoftware.org/t/attachment-save-errors/1126/2

@melton-jason
Copy link
Contributor

Perhaps a duplicate of #2409 and #2477.
Also related to #2525

@maxpatiiuk
Copy link
Member

@grantfitzsimmons can you please merge the duplicate issues?

@grantfitzsimmons
Copy link
Member Author

Once you fix the issue 😉 /s

Will do!

@grantfitzsimmons
Copy link
Member Author

This does not happen with a new CT record.

image

https://willemcatno-edge.test.specifysystems.org/specify/view/collectingtrip/1057/

Steps to Recreate:

  1. Edit the Latitude field (text5)
    (I have been able to recreate this with any field, but this was in the initial bug report)

  2. Save

  3. See error:

Invalid response code 500. Expected one of 200, 201, and 409. Response:
TypeError at /api/specify/collectingtrip/1057/
cannot unpack non-iterable NoneType object

Same database as #2409. Seems to be a back-end error, but needs to be resolved ASAP.

Reported By: Willem at SAIAB

@grantfitzsimmons
Copy link
Member Author

Screen.Recording.2022-11-04.at.1.39.23.PM.mp4

This is a problem in edge and testability.

TypeError at /api/specify/storage/91843/
cannot unpack non-iterable NoneType object

Specify 7 Crash Report - 2022-11-04T18_42_26.470Z.txt

@grantfitzsimmons grantfitzsimmons changed the title Cannot save Collecting Trip with an attachment Cannot save Collecting Trip or Storage with an attachment Apr 3, 2023
@grantfitzsimmons
Copy link
Member Author

@melton-jason Is the fix you used here relevant to these issues?

#2744

@melton-jason
Copy link
Contributor

@melton-jason Is the fix you used here relevant to these issues?

#2744

I believe so. I have not looked at the Storage or Collecting Trip issue and uncovered the cause, but I think it is the same issue which means the fix should be the same as the fix for exchange in/exchange out.

The problem occurs as an attachment business rule problem and occurs at this line.

obj.attachment.scopetype, obj.attachment.scopeid = Scoping(attachee)()

More specifically, Scoping(attachee)() is returning None, which python then tries to unpack into the scopetype and scopeid of the object's attachment

For quick reference, here is the Scoping class.

class Scoping(namedtuple('Scoping', 'obj')):
def __call__(self):
table = self.obj.__class__.__name__.lower()
scope = getattr(self, table, lambda: None)()
if scope is None:
inferred_scope = self._infer_scope()
if inferred_scope is None: return self._default_institution_scope()
return scope
################################################################################
def accession(self):
institution = models.Institution.objects.get()
if institution.isaccessionsglobal:
return INSTITUTION_SCOPE, institution.id
else:
return self._simple_division_scope()
def agent(self): return self._simple_division_scope()
def borrow(self): return self._simple_collection_scope()
def collectingevent(self): return self._simple_discipline_scope()
def collectionobject(self): return self._simple_collection_scope()
def conservdescription(self): return self._simple_division_scope()
def conservevent(self): return Scoping(self.obj.conservdescription)()
def dnasequence(self): return self._simple_collection_scope()
def dnasequencing(self): return self._simple_collection_scope()
def exchangein(self): return self._simple_division_scope()
def exchangeout(self): return self._simple_division_scope()
def fieldnotebook(self): return self._simple_discipline_scope()
def fieldnotebookpage(self): return Scoping(self.obj.pageset)()
def fieldnotebookpageset(self): return Scoping(self.obj.fieldnotebook)()
def gift(self): return self._simple_discipline_scope()
def loan(self): return self._simple_discipline_scope()
def locality(self): return self._simple_discipline_scope()
def permit(self):
return INSTITUTION_SCOPE, self.obj.institution_id
def preparation(self): return self._simple_collection_scope()
def referencework(self):
institution = models.Institution.objects.get()
return INSTITUTION_SCOPE, institution.id
def repositoryagreement(self): return self._simple_division_scope()
def taxon(self):
return DISCIPLINE_SCOPE, self.obj.definition.discipline.id
#############################################################################
def _simple_discipline_scope(self):
return DISCIPLINE_SCOPE, self.obj.discipline_id
def _simple_division_scope(self):
return DIVISION_SCOPE, self.obj.division_id
def _simple_collection_scope(self):
return COLLECTION_SCOPE, self.obj.collectionmemberid
def _infer_scope(self):
if hasattr(self.obj, "division_id"): return self._simple_division_scope()
if hasattr(self.obj, "discipline_id") : return self._simple_discipline_scope()
if hasattr(self.obj, "collectionmemberid"): return self._simple_collection_scope()
# If the table has no scope, and scope can not be inferred then scope to institution
def _default_institution_scope(self):
institution = models.Institution.objects.get()
return INSTITUTION_SCOPE, institution.id

I tried to alleviate potential issues like this in #2744 by implementing a way to infer the table's scoping, but (obviously) it seems there is a bug in the code.
According to the code's intended behavior, Collecting Trip (and thus its attachments) would be scoped to Discipline.

Aside from also fixing the bug, I will add scoping tests on the backend to reduce the possibility of this happening again.

@maxpatiiuk
Copy link
Member

That scoping.py file is criminally bug-prone.
@melton-jason Can it be refactored to scan whether a table has collectionmemberid, division_id or discipline_id or etc and automatically determine scope based on that rather than have the programmer manually add scope like this to every table?

def locality(self): return self._simple_discipline_scope()

It is needlesly bug prone and causes issues like #2525

@melton-jason
Copy link
Contributor

melton-jason commented Apr 3, 2023

That scoping.py file is criminally bug-prone. @melton-jason Can it be refactored to scan whether a table has collectionmemberid, division_id or discipline_id or etc and automatically determine scope based on that rather than have the programmer manually add scope like this to every table?

def locality(self): return self._simple_discipline_scope()

It is needlesly bug prone and causes issues like #2525

@maxpatiiuk
That is what I tried to do by adding an _infer_scope() method in #2744: it checks if the object has collectionmemberid, division_id, or discipline_id attributes and returns the respective scoping. If the object does not have any of those attributes, I assume the scoping is institution-wide.
I can fix the bug, add testing, then remove the redundant table methods and save that space for any tables that we wish to manually override.

@maxpatiiuk
Copy link
Member

oh that would be awesome!
I didn't notice that. Thanks!

@grantfitzsimmons
Copy link
Member Author

Same issue happens with DNA Sequencing Run attachments

@emenslin
Copy link
Collaborator

emenslin commented Aug 5, 2024

Fixed

@emenslin emenslin closed this as completed Aug 5, 2024
@github-project-automation github-project-automation bot moved this from 📋 Backlog to ✅ Done in Back-End Backlog Aug 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1 - Bug Incorrect behavior of the product
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

5 participants