membermatters / MemberMatters

An open source membership, access and payments portal for makerspaces and community groups.
https://membermatters.org
MIT License
40 stars 23 forks source link

Error when trying to add interlock/door to platform #210

Closed proffalken closed 9 months ago

proffalken commented 10 months ago

Describe the bug

When trying to use the firmware from https://github.com/membermatters/mainboard-firmware, the platform errors with the following in the MemberMatters log:

membership    | 127.0.0.1:32848 - - [04/Sep/2023:19:08:33] "WSCONNECTING /ws/access/door/<MAC_ADDR>" - -
membership    | 2023-09-04 19:08:33,173 app INFO     Door connected!
membership    | 2023-09-04 19:08:33,437 daphne.server ERROR    Exception inside application: Doors matching query does not exist.
membership    | Traceback (most recent call last):
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 71, in __call__
membership    |     return await application(scope, receive, send)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/sessions.py", line 47, in __call__
membership    |     return await self.inner(dict(scope, cookies=cookies), receive, send)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/sessions.py", line 263, in __call__
membership    |     return await self.inner(wrapper.scope, receive, wrapper.send)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/auth.py", line 185, in __call__
membership    |     return await super().__call__(scope, receive, send)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/middleware.py", line 26, in __call__
membership    |     return await self.inner(scope, receive, send)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 150, in __call__
membership    |     return await application(
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 150, in __call__
membership    |     return await application(
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 150, in __call__
membership    |     return await application(
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/consumer.py", line 94, in app
membership    |     return await consumer(scope, receive, send)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/consumer.py", line 58, in __call__
membership    |     await await_many_dispatch(
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/utils.py", line 51, in await_many_dispatch
membership    |     await dispatch(result)
membership    |   File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 479, in __call__
membership    |     ret: _R = await loop.run_in_executor(
membership    |   File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
membership    |     result = self.fn(*self.args, **self.kwargs)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/db.py", line 13, in thread_handler
membership    |     return super().thread_handler(loop, *args, **kwargs)
membership    |   File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 538, in thread_handler
membership    |     return func(*args, **kwargs)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/consumer.py", line 125, in dispatch
membership    |     handler(message)
membership    |   File "/usr/local/lib/python3.10/site-packages/channels/generic/websocket.py", line 38, in websocket_connect
membership    |     self.connect()
membership    |   File "/usr/src/app/memberportal/api_access/consumers.py", line 60, in connect
membership    |     self.door = Doors.objects.get(serial_number=self.door_id)
membership    |   File "/usr/local/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
membership    |     return getattr(self.get_queryset(), name)(*args, **kwargs)
membership    |   File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 435, in get
membership    |     raise self.model.DoesNotExist(
membership    | access.models.Doors.DoesNotExist: Doors matching query does not exist.

I get the same error when I try to create the door/interlock via the admin UI as well, so this seems to be an issue with MemberMatters rather than with the mainboard firmware.

To Reproduce Steps to reproduce the behavior:

  1. Install the Firmware
  2. Power up the device
  3. View the logs
  4. See error

Expected behavior

The Door object is created if it does not already exist, and the mainboard is registered with the platform.

Additional context

I suspect this is due to Doors inheriting from AccessControlDevice but for some reason it's trying to look up its own ID which doesn't exist and then fails.

jabelone commented 10 months ago

I’m just heading to bed, and can take a proper look tomorrow, but from memory it was a specific decision that doors had to be added before they could connect. In the interest of security and not having random doors connecting etc. to your instance, the web socket API will only allow the connection if the door exists and the serial number (ie MAC address, but could be any fixed identifier) matches. Ideally we’d have some sort of discovery / approval queue but that was more work I didn’t have time for. ;)

you can see the logic here: https://github.com/membermatters/MemberMatters/blob/b1c363e00e39dc338c74a77df704c643904f2bfd/memberportal/api_access/consumers.py#L49

tl;dr If you add the door to MM first, and make sure the serial number matches, it should allow the door to connect. If you think this behaviour should be changed, happy to have a discussion. :)

proffalken commented 10 months ago

The logic is sound, the problem is that I get the same error when I try and add a door via the admin interface and I can't see a button for it on the "main" interface.

I'll have another look and see, thanks :)

jabelone commented 10 months ago

Ohh I see. Sorry thought it was just on websocket connect. I’ll take a look tomorrow some time. :)

jabelone commented 10 months ago

For tomorrow, can you post the stack trace from the request when you try to add a new door? I suspect it’s something from here , but the stacktrace error from that specific request will help narrow it down.

proffalken commented 10 months ago

Sure, same error:

Environment:

Request Method: POST
Request URL: https://members.makemonmouth.co.uk/admin/access/doors/add/

Django Version: 3.2.20
Python Version: 3.10.13
Installed Applications:
['constance',
 'constance.backends.database',
 'django_prometheus',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.humanize',
 'oidc_provider',
 'channels',
 'profile',
 'access',
 'group',
 'memberbucks',
 'api_spacedirectory',
 'api_general',
 'api_access',
 'api_meeting',
 'api_admin_tools',
 'api_billing',
 'corsheaders',
 'rest_framework',
 'django_celery_results',
 'django_celery_beat']
Installed Middleware:
['django_prometheus.middleware.PrometheusBeforeMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'corsheaders.middleware.CorsMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'membermatters.middleware.Sentry',
 'membermatters.middleware.ForceCsrfCookieMiddleware',
 'django_prometheus.middleware.PrometheusAfterMiddleware']

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 38, in inner
    response = await get_response(request)
  File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 233, in _get_response_async
    response = await wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/contrib/admin/options.py", line 616, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/contrib/admin/sites.py", line 232, in inner
    return view(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/contrib/admin/options.py", line 1657, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
  File "/usr/local/lib/python3.10/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/contrib/admin/options.py", line 1540, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "/usr/local/lib/python3.10/site-packages/django/contrib/admin/options.py", line 1586, in _changeform_view
    self.save_model(request, new_object, form, not add)
  File "/usr/local/lib/python3.10/site-packages/django/contrib/admin/options.py", line 1099, in save_model
    obj.save()
  File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 739, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 763, in save_base
    pre_save.send(
  File "/usr/local/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 180, in send
    return [
  File "/usr/local/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 181, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "/usr/src/app/memberportal/api_access/signals.py", line 24, in save_or_create_door
    door = Doors.objects.get(pk=instance.id)
  File "/usr/local/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 435, in get
    raise self.model.DoesNotExist(

Exception Type: DoesNotExist at /admin/access/doors/add/
Exception Value: Doors matching query does not exist.

POST data:


csrfmiddlewaretoken | <REDACTED>
-- | --
name | 'test'
description | 'test'
ip_address | ''
serial_number | '123456'
last_seen_0 | '05/09/2023'
last_seen_1 | '01:49:25'
all_members | 'on'
post_to_discord | 'on'
report_online_status | 'on'
_save | 'Save'

Thanks in advance!

proffalken commented 10 months ago

213 should provide a fix for this - whether it provides a "good quality" fix is another question... :rofl:

jabelone commented 9 months ago

Forgot to mention this should be fixed now. I went to review your PR (thanks for that!) and realised my original code was using the wrong signal so made some other changes to hopefully fix it "properly".