OK, your example clarified much ... the little function with a passed-
in criteria and returned SQLRows object is simple and clean.
Am I correct in thinking that your example works like this?
# All purchases by 'buyer'--
# All purchases of 'product' by 'buyer'--
def purchased_by(user_id, product_id):
For my understanding, I had to add few .select() and the product_id
arg... am I right? If so, this still requires messing with obtaining
for some attribute we query. It would be nice to have the
db().select be able to use something like
id=db.table.field.find_id('name') to cut out one preliminary step of
assignment... which I guess I could create in the model.
I do see how adding additional attributes to the join table provides
more useful information related to a 'purchase'. My naive baby-step
was only concerned with tagging for now, and in that case an
automagical solution would make sense, but as soon as we start adding
additional dimensions to the problem, the complexity grows
Python has certain design principle 'Golden Rules' which you have
quoted, and I support them. One such guiding principle of Rails is
'Convention over Configuration', meaning that the most common use case
(the 80% of Pareto's Law, or 2 standard deviations) becomes the
default, and the oddball (long tail) can always be made to work with
some extra effort on the programmer's part.
So, why not have an SQLFORM that does the default (conventional)
behavior of simply saving references for the containing table and
partner back to the generic join table? That would handle the majority
of use cases, and remove some ugly hand-work of doing all the DB
manipulation (saving the target, returning the id, then creating the
join with both ids). It would also be a mirror of what SQLFORM does
already, which is why I was kind of thinking it did that already.
Then, once the basic work is done by form.accept and we have a join
reference, the programmer can add other functionality as needed.
# works in one department
# only one worker manages the department
# is assigned to one department
# date worker started on project, how many hours worked
# m2m workers to projects
# naming convention tells web2py which tables join
department new or edit SQLFORM:
- default SELECT dropdown allows change worker (manager)
- just like the Cookbook example - nothing new here
worker new or edit SQLFORM:
- default SELECT dropdown allows change department of worker
- custom SELECT dropdown allows add or remove projects for worker
- (pass project.name
field to SQLFORM, web2py parses table names
to deduce join table name)
project new or edit SQLFORM:
- default SELECT dropdown allows change department of project
- custom SELECT dropdown allows add or remove worker for project
- (pass worker.name
field to SQLFORM, web2py parses table names to
deduce join table name)
So that makes the basic functionality. I suppose one could do more
with join table fields, like the datestamp thing, but I can't see that
far right now.
Suppose you visit a worker edit page and add a project. If you went
over and visited the project next, you would then see the worker, so
the assignments are actually mirror images of each other.
I did something like this for my Rails app, and using AJAX, I made a
little area that showed all the workers for a project, so as you made
updates, the list would change. Cute!
In conclusion, it seems that without this 'balancing' of SQLFORM for
the m2m case, its utility is somewhat limited. We, the programmers,
are forced to get our hands dirty with all the db resulting
manipulation, which web2py has spoiled us for by doing so much magic
Here is some sophisticated discussion on how one other web framework
is solving problems like this: