emmett-framework / emmett

The web framework for inventors
BSD 3-Clause "New" or "Revised" License
1.09k stars 72 forks source link

Trying to do list:reference returns error #472

Open SvenKeimpema opened 1 year ago

SvenKeimpema commented 1 year ago

i was trying to do a list:reference to a table however i noticed that the has_many isn't an actual field(which is needed for it to go into a form). whenever i tried adding a list of references to refers_to it also threw an error:

TypeError: int() argument must be a string, a bytes-like object or a real number, not 'list'

full traceback:

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/emmett/rsgi/handlers.py", line 203, in dynamic_handler
    http = await self.router.dispatch(request, response)
  File "/usr/local/lib/python3.11/site-packages/emmett/routing/router.py", line 249, in dispatch
    return await match.dispatch(reqargs, response)
  File "/usr/local/lib/python3.11/site-packages/emmett/routing/dispatchers.py", line 72, in dispatch
    rv = self.response_builder(await self.f(**reqargs), response)
  File "/usr/local/lib/python3.11/site-packages/emmett/pipeline.py", line 328, in flow
    output = await pipe_method(f, **kwargs)
  File "/usr/local/lib/python3.11/site-packages/emmett/pipeline.py", line 234, in pipe
    return await next_pipe(**kwargs)
  File "/usr/local/lib/python3.11/site-packages/emmett/pipeline.py", line 328, in flow
    output = await pipe_method(f, **kwargs)
  File "/usr/local/lib/python3.11/site-packages/emmett/tools/auth/apis.py", line 277, in pipe
    return await next_pipe(**kwargs)
  File "/usr/local/lib/python3.11/site-packages/emmett/pipeline.py", line 369, in flow
    return await pipe_method(f, **kwargs)
  File "/usr/local/lib/python3.11/site-packages/emmett/pipeline.py", line 274, in pipe_request
    return await next_pipe(**kwargs)
  File "/app/testem/controllers/microlearnings.py", line 27, in microlearning_content
    grid = await SQLFORM.grid(query, GridSettings())
  File "/app/testem/tools/sqlgrid.py", line 129, in grid
    return await cls.__insert_form(query_helper, grid_settings)
  File "/app/testem/tools/sqlgrid.py", line 152, in __insert_form
    form = await model.form()
  File "/usr/local/lib/python3.11/site-packages/emmett/forms.py", line 399, in _process
    await super()._process(write_defaults=False)
  File "/usr/local/lib/python3.11/site-packages/emmett/forms.py", line 165, in _process
    self._validate_input()
  File "/usr/local/lib/python3.11/site-packages/emmett/forms.py", line 383, in _validate_input
    record.update(fields)
  File "/usr/local/lib/python3.11/site-packages/emmett/orm/objects.py", line 1438, in update
    self.__setattr__(key, val)
  File "/usr/local/lib/python3.11/site-packages/emmett/orm/objects.py", line 1381, in __setattr__
    object.__setattr__(self, key, value)
  File "/usr/local/lib/python3.11/site-packages/emmett/orm/models.py", line 1158, in __set__
    val = typed_row_reference(val, self.table)
  File "/usr/local/lib/python3.11/site-packages/emmett/orm/helpers.py", line 558, in typed_row_reference
    return {
  File "/usr/local/lib/python3.11/site-packages/emmett/orm/helpers.py", line 121, in __new__
    rv = super().__new__(cls, id, *args, **kwargs)
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'list'

is there any way to get a list of references without using has_many or can you make it so has_many works in forms?

SvenKeimpema commented 1 year ago

@gi0baro having a list:reference is pretty essential for what i'm currently making and without this i really can't move foward and can't continue using emmett...

gi0baro commented 1 year ago

@SvenKeimpema I'm sorry this blocks you, but there's really not so much Emmett can do for you at the moment on many relations and forms.

The proper way at the moment would be to manually handle the multiple forms on your own, and the relevant add/remove logic in the template as well. Note that you should be able to make all the forms you want in a single page, eg:

@app.route()
async def my_forms():
    form = await ModelA.form()
    form_related = await ModelB.form()
    return locals()

in the upper code, you can manually set the ModelA reference using a custom onvalidation on the form of ModelB.

Without further information about your use-case I'm afraid there's no other option here.

SvenKeimpema commented 1 year ago

@gi0baro what i currenly mean is that if i have a form for say ModelA.form() and i want it to have many references to ModelB like:

class ModelB(Model):
   table_name="model_b"
   random_field = Field.text(...)

class ModelA(Model):
    refers_to({'modelb_ids', 'model_b'})

    validation = {
        'modelb_ids': {
            'in': {
                'dbset': lambda db: db.where(db.model_b.id>=0),
                'orderby': lambda row: row.random_field,
                'label_field': 'random_field'
            },
            'multiple': True
        }
    }

this will return an error if i insert [1, 2](which are the id's of modelB) into ModelA, For example:

ModelB.create(random_field="1")
ModelB.create(random_field="2")
ModelA.create(modelb_ids=[1, 2])

(it will throw the error i typed above)

gi0baro commented 1 year ago

@SvenKeimpema yes, that's correct. As per documentation refers_to and belongs_to are 1:1 relationships.

1:N relations are provided by has_many, and considering what you described, you probably want to make the inverse relationship:

class ModelA(Model):
    has_many({'modelb_ids': 'ModelB'})

class ModelB(Model):
    refers_to({'model_a': 'ModelA'})
    random_field = Field.text()

so then you can:

a = ModelA.create()
ModelB.create(random_field="1", model_a=a.id)
ModelB.create(random_field="2", model_a=a.id)
SvenKeimpema commented 1 year ago

@gi0baro not exactly, yes i want a N->1 relation however if i use has_many in a form

ModelA.form()

it will not show the has_many field in the form, which in the form i want to be able to select relations to said form. for example if there are 3 fields in ModelB

ModelB.create(random_field="1", model_a=a.id)
ModelB.create(random_field="2", model_a=a.id)
ModelB.create(random_field="3", model_a=a.id)

i want to be able to select those fields in the form when i am in ModelA. Like when i am on the form i want to be able to select the reference random_field 1 and random field 2. AKA modelA will make 2 relations to modelB

gi0baro commented 1 year ago

@SvenKeimpema I get your point, the thing IMHO is that you need to decide which side of the 1:N relationship is 1 and which one is N.

If the relationship is 1:N for ModelA:ModelB, and you want to create ModelB from ModelA in forms you have 2 options with Emmett 2.5:

If you end-up with a general-purpose solution for this, maybe you can create an Extension providing such components :)