I am using web2py, and am trying to build a field for auth_user, that should be validated to be a member of a certain group. So, in the models/db.py I have added a field that tells who is the manager of the user:
auth.settings.extra_fields['auth_user']=[Field('manager', 'reference auth_user')]
Then I have set up to db.auth_user, db.auth_group and db.auth_membership to contain users that belong to group 'managers'
And what I would like now to achieve is to validate user input so, that the 'manager' field of the auth_user could contain only users from the group 'managers'. I have gone through quite a few variations, following is maybe closest to making sense in theory in my mind:
group_id = auth.id_group('managers')
all_users_in_group = db(db.auth_membership.group_id==group_id)._select(db.auth_membership.user_id)
db.auth_user.auditor.requires = IS_IN_DB(db, db(~db.auth_user.id.belongs(all_users_in_group)).select(db.auth_user.id))
But even that is failing with
<type 'exceptions.AttributeError'>('Table' object has no attribute 'managers')
A perfect solution to my problem would show in the drop down menu not auth_user.id, but auth_user.first_name concatenated with auth_user.last_name.
I have used similar code to the following and confirm that is works with web2py 2.17.2-stable+timestamp.2018.10.06.18.54.02 (Running on nginx/1.14.0, Python 3.6.7).The previous answer will not work for this version.
group_id = auth.id_group('managers')
user_rows = db(db.auth_membership.group_id == group_id)._select(db.auth_membership.user_id)
query = db(db.auth_user.id.belongs(user_rows))
db.auth_user.auditor.requires = IS_IN_DB(query, db.auth_user.id, '%(first_name)s %(last_name)s'
You have done it correctly in your answer, but you can improve it.
The first argument of the validator can be a database connection or a DAL Set. So you can pass
directly a db query as first argument, like this
query = db((db.auth_membership.group_id == auth.id_group('managers')) &
(db.auth_membership.user_id == db.auth_user.id))
db.auth_user.auditor.requires = IS_IN_DB(query, db.auth_user.id, '%(first_name)s %(last_name)s')
You can also write query like below:
query = db((db.auth_group.role == 'managers') &
(db.auth_membership.group_id == db.auth_group.id) &
(db.auth_membership.user_id == db.auth_user.id))
I actually managed to solve this, but I would definitely not call this elegant. Would there be more idiomatic way to do this in web2py? Following is what I added to models/db.py
group_id = auth.id_group('managers')
rows=db(db.auth_membership.group_id==group_id).select(db.auth_membership.user_id)
rset=set()
for r in rows:
rset.add(int((r.user_id)))
db.auth_user.auditor.requires = IS_IN_DB(db(db.auth_user.id.belongs(rset)), db.auth_user.id, '%(first_name)s %(last_name)s')
Related
Using an example close to the one from the many to many relationship example in the book:
db = DAL("sqlites://storage.sqlite")
db.define_table('persons',
Field('name'))
db.define_table('things',
Field('name'))
db.define_table('ownership',
Field('person', 'reference persons'),
Field('thing', 'reference things'))
I have an additional table :
db.define_table('usages',
Field('person', 'reference person',requires = IS_IN_DB(db, db.person.id, '%(name)s'),
Field('thing', 'reference thing'),
Field('usage'))
For the table usages I want to have dropdown menus for both person and thing fields hence the use of requires .....
However I would like to have the dropdown menu from thing to changes according to the selection in person (to limit the choice to things a person owns).
I thought about using requires as i did to get the dropdown menu, however I cannot use db.usages.person in a requires = IS_IN_DB(...) because the entry is not created and therefore it throws an exception.
What I tried :
requires = IS_IN_DB(db, db((db.usages.person == db.persons.id) &(db.persons.id == db.ownership.person) &(db.ownership.person == db.things.name)))
Using requires = IS_IN_DB(db,db.things.id, '%(name)s') gives me a dropdown menu for thing but it lists all of the them regardless of the selection in person.
Any idea on how to solve this?
There is no built-in way to do what you want. You will need to use Javascript to (a) detect when a selection is made in the person field, and (b) make an Ajax request to the server to fetch a list of options for the thing select element.
For some examples, check out these posts on the web2pyslices site, and also check out plugin_lazy_options_widget.
Also, regarding your code:
requires = IS_IN_DB(db, db((db.usages.person == db.persons.id) &(db.persons.id == db.ownership.person) &(db.ownership.person == db.things.name)))
note, that the second argument to IS_IN_DB must be a Field object or field name, not a DAL Set object. If you want to limit the set of options via a filter, the first argument can be a Set object (though, as noted, that won't help in this case, as you don't know what the filter criterion will be until a selection is made in the browser).
I'm trying to use .extra() function with .related_table():
foo_objects = Foo.objects.all()
result = foo.extra(select={'is_ok':'IF(bar.is_ok,"Yes","No")'}).select_related('bar')
Foo and Bar are connected (Foo has bar_id) with models and everything,
but I keep getting "Unknown column 'bar.is_ok' in 'field list'" when calling result.values(),
Looking at the Query generated (the actual query produced, not foo.query), it doesn't
seem to join the two, any ideas on how I do that ?
The following query ought to work, but I can't really test it...
foo_objects = Foo.objects.select_related('bar').extra(select={'is_ok':'IF(bar.is_ok,"Yes","No")'})
It doesn't matter which order you do the select_related() and extra() in, as long as they're both on the same queryset.
Update
If you need it to work with a ValuesQuerySet, you can't use select_related(), so you have to do it slightly differently, by using additional parameters to the extra()...
foo_objects = Foo.objects.extra(tables=('bar',),
where=('foo.bar_id=bar.id',),
select={'is_ok':'IF(bar.is_ok,"Yes","No")'}).values()
...or if you don't need "Yes" and "No" back, you can just use...
foo_objects = Foo.objects.values('bar__is_ok')
...which will force the join.
See also Django ticket #3358.
I'm writing a quick and dirty maintenace script to delete some rows and would like to avoid having to bring my ORM classes/mappings over from the main project. I have a query that looks similar to:
address_table = Table('address',metadata,autoload=True)
addresses = session.query(addresses_table).filter(addresses_table.c.retired == 1)
According to everything I've read, if I was using the ORM (not 'just' tables) and passed in something like:
addresses = session.query(Addresses).filter(addresses_table.c.retired == 1)
I could add a .delete() to the query, but when I try to do this using only tables I get a complaint:
File "/usr/local/lib/python2.6/dist-packages/sqlalchemy/orm/query.py", line 2146, in delete
target_cls = self._mapper_zero().class_
AttributeError: 'NoneType' object has no attribute 'class_'
Which makes sense as its a table, not a class. I'm quite green when it comes to SQLAlchemy, how should I be going about this?
Looking through some code where I did something similar, I believe this will do what you want.
d = addresses_table.delete().where(addresses_table.c.retired == 1)
d.execute()
Calling delete() on a table object gives you a sql.expression (if memory serves), that you then execute. I've assumed above that the table is bound to a connection, which means you can just call execute() on it. If not, you can pass the d to execute(d) on a connection.
See docs here.
When you call delete() from a query object, SQLAlchemy performs a bulk deletion. And you need to choose a strategy for the removal of matched objects from the session. See the documentation here.
If you do not choose a strategy for the removal of matched objects from the session, then SQLAlchemy will try to evaluate the query’s criteria in Python straight on the objects in the session. If evaluation of the criteria isn’t implemented, an error is raised.
This is what is happening with your deletion.
If you only want to delete the records and do not care about the records in the session after the deletion, you can choose the strategy that ignores the session synchronization:
address_table = Table('address', metadata, autoload=True)
addresses = session.query(address_table).filter(address_table.c.retired == 1)
addresses.delete(synchronize_session=False)
I'm just using SQLAlchemy core, and cannot get the sql to allow me to add where clauses. I would like this very generic update code to work on all my tables. The intent is that this is part of a generic insert/update function that corresponds to every table. By doing it this way it allows for extremely brief test code and simple CLI utilities that can simply pass all args & options without the complexity of separate sub-commands for each table.
It'll take a few more tweaks to get it there, but should be doing the updates now just fine. However, while SQLAlchemy refers to generative queries it doesn't distinguish between selects & updates. I've reviewed SQLAlchemy documentation, Essential SQLAlchemy, stackoverflow, and several source code repositories, and have found nothing.
u = self._table.update()
non_key_kw = {}
for column in self._table.c:
if column.name in self._table.primary_key:
u.where(self._table.c[column.name] == kw[column.name])
else:
col_name = column.name
non_key_kw[column.name] = kw[column.name]
print u
result = u.execute(kw)
Which fails - it doesn't seem to recognize the where clause:
UPDATE struct SET year=?, month=?, day=?, distance=?, speed=?, slope=?, temp=?
FAIL
And I can't find any examples of building up an update in this way. Any recommendations?
the "where()" method is generative in that it returns a new Update() object. The old one is not modified:
u = u.where(...)
Just diving into pylons here, and am trying to get my head around the basics of SQLALchemy. I have figured out how to load a record by id:
user_q = session.query(model.User)
user = user_q.get(user_id)
But how do I query by a specific field (i.e. username)? I assume there is a quick way to do it with the model rather than hand-building the query. I think it has something with the add_column() function on the query object, but I can't quite figure out how to use it. I've been trying stuff like this, but obviously it doesn't work:
user_q = meta.Session.query(model.User).add_column('username'=user_name)
user = user_q.get()
I believe you want something like this:
user = meta.Session.query(model.User).filter_by(name=user_name).first()
Which is short for this:
user = meta.Session.query(model.User).filter(model.User.name=user_name).first()
Here's more documentation
If you change first() into one() it will raise an exception if there isn't a user (if you want to assert one exists).
I highly suggest reading through both Object Relational Tutorial as well as the and the SQL Expression Language Tutorial.