Say I have User and Subuser tables, where a User can have many Subusers. How can I only allow the logged in user (current_user) to view only the Subusers who are a part of the User? I.e. Subuser.user_id == current_user.id. I get I can do this by a filter, but this must be forced and not optional.
I'm using the SQLAlchemy backend if that helps!
It's pretty simple to override the queries that are produced by the backend in Flask Admin - you can do some quite complex things. Remember to override all of the below - especially get_one().
class BaseModelView(ModelView):
def is_accessible(self):
# if not authenticated we can't see this
if not login.current_user.is_authenticated():
return False
return True
def get_query(self):
return query.filter(self.model.parent_user_id.any(id=login.current_user.id))
def get_count_query(self):
# faster count query!
query = self.session.query(func.count('*')).select_from(self.model).filter(self.model.location_id == login.current_user.id)
return query
def get_one(self, id):
"""
Return a single model by its id.
:param id:
Model id
"""
result = self.session.query(self.model).get(iterdecode(id))
# if the users location does not exist and she is only allowed to edit locations they control
if login.current_user.id != result.parent_user_id:
app.logger.warn('You are not allowed to edit entries for this user.')
abort(404)
return result
Related
So I'm just trying to make sense of the output of the SQLAlchemy ORM methods after creating a model, committing some entries and running queries. Most queries are fine...I'm getting back a list but for some it just returns an object (see below). I know this sounds obvious but is this normal behavior? I'm specifically referring to the filter_by query as you can see below...
#sample_app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI']='...'
db = SQLAlchemy(app)
class Person(db.Model):
__tablename__='persons'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), nullable=False)
def __repr__(self):
return f'<Person Id: {self.id}, name: {self.name}>'
db.create_all()
#Run some basic commands in interactive mode with model already populated
python3
from sample_app import db,Person
#add a bunch of persons
person1=Person(name='Amy')
person2=...
db.session.add(person1)
db.session.commit()
...
#Run queries
Person.query.all() #returns all persons as a list
Person.query.first() #returns first item in the list
Person.query.filter_by(name='Amy')
#returns <flask_sqlalchemy.Basequery object at 0xsadfjasdfsd>
So why am I not getting the same type of output for the third query for 'Amy'? is that normal behavior for the filter_by method?
Thanks
You didn’t execute the query in the last example. The all method brings back all object selected by the query, first is the first. You’ve specified a filter in the last example, but you didn’t execute a method which processes the query and returns a result [set].
If there are more than one Amy’s, you get all the matches with all() or the first with first(). If you had a filter which should yield a unique record, you could also use .one()
We are creating a service for an app using tornado and sqlalchemy. The application is written in django and uses a "soft delete mechanism". What that means is that there was no deletion in the underlying mysql tables. To mark a row as deleted we simply set the attributed "delete" as True. However, in the service we are using sqlalchemy. Initially, we started to add check for delete in the queries made through sqlalchemy itself like:
customers = db.query(Customer).filter(not_(Customer.deleted)).all()
However this leads to a lot of potential bugs because developers tend to miss the check for deleted in there queries. Hence we decided to override the default querying with our query class that does a "pre-filter":
class SafeDeleteMixin(Query):
def __iter__(self):
return Query.__iter__(self.deleted_filter())
def from_self(self, *ent):
# override from_self() to automatically apply
# the criterion too. this works with count() and
# others.
return Query.from_self(self.deleted_filter(), *ent)
def deleted_filter(self):
mzero = self._mapper_zero()
if mzero is not None:
crit = mzero.class_.deleted == False
return self.enable_assertions(False).filter(crit)
else:
return self
This inspired from a solution on sqlalchemy docs here:
https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/PreFilteredQuery
However, we are still facing issues, like in cases where we are doing filter and update together and using this query class as defined above the update does not respect the criterion of delete=False when applying the filter for update.
db = CustomSession(with_deleted=False)()
result = db.query(Customer).filter(Customer.id == customer_id).update({Customer.last_active_time: last_active_time })
How can I implement the "soft-delete" feature in sqlalchemy
I've done something similar here. We did it a bit differently, we made a service layer that all database access goes through, kind of like a controller, but only for db access, we called it a ResourceManager, and it's heavily inspired by "Domain Driven Design" (great book, invaluable for using SQLAlchemy well). A derived ResourceManager exists for each aggregate root, ie. each resource class you want to get at things through. (Though sometimes for really simple ResourceManagers, the derived manager class itself is generated dynamically) It has a method that gives out your base query, and that base query gets filtered for your soft delete before it's handed out. From then on, you can add to that query generatively for filtering, and finally call it with query.one() or first() or all() or count(). Note, there is one gotcha I encountered for this kind of generative query handling, you can hang yourself if you join a table too many times. In some cases for filtering we had to keep track of which tables had already been joined. If your delete filter is off the primary table, just filter that first, and you can join willy nilly after that.
so something like this:
class ResourceManager(object):
# these will get filled in by the derived class
# you could use ABC tools if you want, we don't bother
model_class = None
serializer_class = None
# the resource manager gets instantiated once per request
# and passed the current requests SQAlchemy session
def __init__(self, dbsession):
self.dbs = dbsession
# hand out base query, assumes we have a boolean 'deleted' column
#property
def query(self):
return self.dbs(self.model_class).filter(
getattr(self.model_class, 'deleted')==False)
class UserManager(ResourceManager):
model_class = User
# some client code might look this
dbs = SomeSessionFactoryIHave()
user_manager = UserManager(dbs)
users = user_manager.query.filter_by(name_last="Duncan").first()
Now as long as I always start off by going through a ResourceManager, which has other benefits too (see aforementioned book), I know my query is pre-filtered. This has worked very well for us on a current project that has soft-delete and quite an extensive and thorny db schema.
hth!
I would create a function
def customer_query():
return db.session.query(Customer).filter(Customer.deleted == False)
I used query functions to not forget default flags, to set flags based on user permission, filter using joins etc, so that these things wont be copy-pasted and forgotten at various places.
I am gathering all of the users who have administrative privileges with #admins = User.find_by(admin: true), but when I try and get the number of admins with puts #admins.size, I get the error NoMethodError: undefined method 'size' for #<User:0x00000009b78988>. I expect to get just 1. Any idea what's going wrong?
You're expecting it to return a list of all the matching Users, but it doesn't -- find_by "finds the first record matching the specified conditions."
You can use User.where(admin: true) instead, and that should work as you intend, returning an Array of all the Users where admin is true.
as mentioned, find_by will only return the first instance of record matching the given condition.
Since you want a list of all the admin, use where.
Also, you can turn this into a scope in the User model as follow:
scope :admins, -> { where(admin: true) }
And then call:
User.admins # gets a list of all the admins
User.admins.size # or User.admins.count to return the number of admins you have.
find_by will return only the first record matching the conditions. What you want here is probably where
#admins = User.where(admin: true)
#admins.size
I have a Profile model with a OneToOne relationship with a User. For such Profile model I use the following class, which uses two additional fields coming directly from the User:
class ProfileResource(ModelResource):
username = fields.CharField(attribute='user__username')
email = fields.CharField(attribute='user__email')
class Meta:
# Base data
queryset = Profile.objects.all()
# Allowed methods
list_allowed_methods = ['get']
detail_allowed_methods = ['get', 'put', 'patch']
# Security setup (FEEBLE)
authorization = Authorization()
authentication = BasicAuthentication()
Such resource works fine when consulting profiles. It retrieves the username and email perfectly, and is capable of filtering and ordering by such parameters.
However, I cannot manage to update such fields on the User model in a, say, elegant fashion. All I have come up with is this:
def obj_update(self, bundle, skip_errors=False, **kwargs):
bundle = super().obj_update(bundle, skip_errors=skip_errors, **kwargs)
if 'username' in bundle.data: bundle.obj.user.username = bundle.data['username']
if 'email' in bundle.data: bundle.obj.user.email = bundle.data['email']
bundle.obj.user.save()
return bundle
Which works fine but doesn't seem the best of solutions.
Does anybody know a better way to work out this kind of field-only relationship between a resource and a related model?
Is there any way to restrict the number of records to be created for a single object or table
My requirement needs me to create only 3 contacts for my company. Is there any way to do so.
Thanks & Regards,
Atchuthan
You can use any one of the following method to restrict the number of records created.
1. Restrict from postgresql-- I dont know how to implement this
2. Restrict from python side.: Override your create method of your model and add check condition, if the limit is reached, then raise a warning.
For example, if you want to only to create max 5 users, then inherit res.users model,
class users(osv.osv):
_inherit="res.users"
def create(cr, uid, default, context=None):
res = super(user, self).create(cr, uid, default, context)
if len(self.search(cr, uid, [])) > 5:
raise osv_except('Error','User Limt exceeded')
return res
By default the limit is 80, however you can change this within this file:
web/addons/web/static/src/js/view_list.js
Full documentation on how to do it is at this website:
http://help.openerp.com/question/6627/how-set-limit-for-number-records-of-x2many/