Search 2 tables simultaneously using flask-executor - flask

I have 2 large postgres tables which have an index so that I can perform a full text search on each.
Typically, they look like:
class Post_1(db.Model):
query_class = PostQuery
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
content = db.Column(db.Text)
datestamp = db.Column(db.Float)
search_vector = db.Column(TSVectorType('title', 'content'))
and
class Post_2(db.Model):
query_class = PostQuery
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
content = db.Column(db.Text)
datestamp = db.Column(db.Float)
search_vector = db.Column(TSVectorType('title', 'content'))
In my flask application, to get the documents which have a specific keyword in one of the tables, I would do:
Post_1.query.search(keyword).\
order_by(Post_1.datestamp.desc()).limit(1)
Since I want to run the same search simultaneously on both tables, I wanted to use flask-executor and wrote the following code:
from flask_executor import Executor
executor = Executor(app)
futures=[]
keyword = "covid"
future=executor.submit(Post_1.query.search(keyword).\
order_by(Post_1.datestamp.desc()).limit(1))
futures.append(future)
future = executor.submit(Post_2.query.search(keyword).\
order_by(Post_2.datestamp.desc()).limit(1))
futures.append(future)
This does not work and I get the following error:
RuntimeError: This decorator can only be used at local scopes when a request context is on the stack. For instance within view functions.
Could anyone help me please?

The error you're getting is because flask-executor is intended to run tasks inside view functions - that is, as part of a request from a user. You're running your code outside of a view (i.e. outside of a scope that would normally be in place when your user is interacting with your application).
Do you need to do this, or is this simply part of a test? If you do something like this, just to test it out:
#app.route('/test')
def testroute():
future=executor.submit(Post_1.query.search(keyword).\
order_by(Post_1.datestamp.desc()).limit(1))
futures.append(future)
future = executor.submit(Post_2.query.search(keyword).\
order_by(Post_2.datestamp.desc()).limit(1))
futures.append(future)
Then you should no longer get the error about running outside of a request context, because the code will be running as part of a request (i.e. inside a view function).
As a side note, the SQLAlchemy tasks you're submitting aren't callable - they're not function objects. Executors, whether the one created by Flask-Executor or the vanilla ones you can get via concurrent.futures, expect you to pass a "callable". I suspect your code still wouldn't work unless it was something like:
query = Post_1.query.search(keyword).\
order_by(Post_1.datestamp.desc()).limit(1).all
future = executor.submit(query)
Notice the lack of brackets at the end, because I want to use the callable object itself, not the result it will return
The executor would then "call" the object that had been passed:
executor.submit(Post_1.query.search(keyword).\
order_by(Post_1.datestamp.desc()).limit(1).all()

Related

django.db.transaction.TransactionManagementError: cannot perform saving of other object in model within transaction

Can't seem to find much info about this. This is NOT happening in a django test. I'm using DATABASES = { ATOMIC_REQUESTS: True }. Within a method (in mixin I created) called by the view, I'm trying to perform something like this:
def process_valid(self, view):
old_id = view.object.id
view.object.id = None # need a new instance in db
view.object.save()
old_fac = Entfac.objects.get(id=old_id)
new_fac = view.object
old_dets = Detfac.objects.filter(fk_ent__id__exact = old_fac.id)
new_formset = view.DetFormsetClass(view.request.POST, instance=view.object, save_as_new=True)
if new_formset.is_valid():
new_dets = new_formset.save()
new_fac.fk_cancel = old_fac # need a fk reference to initial fac in new one
old_fac.fk_cancel = new_fac # need a fk reference to new in old fac
# any save() action after this crashes with TransactionManagementError
new_fac.save()
I do not understand this error. I already created & saved a new object in db (when I set the object.id to None & saved that). Why would creating other objects create an issue for further saves?
I have tried not instantiating the new_dets objects with the Formset, but instead explicitely defining them:
new_det = Detfac(...)
new_det.save()
But then again, any further save after that raises the error.
Further details:
Essentially, I have an Entfac model, and a Detfac model that has a foreignkey to Entfac. I need to instantiate a new Enfac (distinct in db), as well as corresponding new Detfac for the new Entfac. Then I need to change some values in some of the fields for both new & old objects, and save all that to db.
Ah. The code above is fine.
But turns out, signals can be bad. I had forgotten that upon saving Detfac, there is a signal that goes to another class and that depending on the circumstances, adds a record to another table (sort of an history table).
Since that signal is just a single operation. Something like that:
#receiver(post_save, sender=Detfac)
def quantity_adjust_detfac(sender, **kwargs):
try:
detfac_qty = kwargs["instance"].qte
product = kwargs["instance"].fk_produit
if kwargs["created"]:
initial = {# bunch of values}
adjustment = HistoQuantity(**initial)
adjustment.save()
else:
except TypeError as ex:
logger.error(f"....")
except AttributeError as ex:
logger.error(f"....")
In itself, the fact that THIS wasn't marked as atomic isn't problematic. BUT if one of those exception throws, THEN I get the transactionmanagementerror. I am still not 100% sure why, tough the django docs do mention that when wrapping a whole view in atomic (or any chunk of code for that matter), then try/except within that block can yield unexpected result, because DJango does rely on exception to decide whether or not to commit the transaction as a whole. And the data I was testing with actually threw the exception (type error when creating the HistoQuantity object).
Wrapping the try/exception with a transaction.atomic manager worked however. Guessing that this... removed/handled the throw, thus the outer atomic could work.

Django - Search matches with all objects - even if they don't actually match

This is the model that has to be searched:
class BlockQuote(models.Model):
debate = models.ForeignKey(Debate, related_name='quotes')
speaker = models.ForeignKey(Speaker, related_name='quotes')
text = models.TextField()
I have around a thousand instances on the database on my laptop (with around 50000 on the production server)
I am creating a 'manage.py' function that will search through the database and returns all 'BlockQuote' objects whose textfield contains the keyword.
I am doing this with the Django's (1.11) Postgres search options in order to use the 'rank' attribute, which sounds like something that would come in handy. I used the official Django fulltext-search documentation for the code below
Yet when I run this code, it matches with all objects, regardless if BlockQuote.text actually contains the queryfield.
def handle(self, *args, **options):
vector = SearchVector('text')
query = options['query'][0]
Search_Instance = Search_Instance.objects.create(query=query)
set = BlockQuote.objects.annotate(rank=SearchRank(vector, query)).order_by('-rank')
for result in set:
match = QueryMatch.objects.create(quote=result, query=Search_Instance)
match.save()
Does anyone have an idea of what I am doing wrong?
I don't see you actually filtering ever.
BlockQuote.objects.annotate(...).filter(rank__gte=0.5)

How to reuse template in Flask-appbuilder with exposed custom handlers?

It is a very specific question regarding Flask-appbuilder. During my development, I found FAB's ModelView is suitable for admin role, but need more user logic handlers/views for complex designs.
There is a many to many relationship between devices and users, since each device could be shared between many users, and each user could own many device. So there is a secondary table called accesses, describes the access control between devices and users. In this table, I add "isHost" to just if the user owns the device. Therefore, we have two roles: host and (regular) user. However, these roles are not two roles defined as other applications, since one man can be either host or user in same time. In a very simple application, enforce the user to switch two roles are not very convinient. That makes things worse.
Anyway, I need design some custom handlers with traditional Flask/Jinja2 templates. For example:
class PageView(ModelView):
# FAB default URL: "/pageview/list"
datamodel = SQLAInterface(Page)
list_columns = ['name', 'date', 'get_url']
#expose("/p/<string:url>")
def p(self, url):
title = urllib.unquote(url)
r = db.session.query(Page).filter_by(name = title).first()
if r:
md = r.markdown
parser = mistune.Markdown()
body = parser(md)
return self.render_template('page.html', title = title, body = body)
else:
return self.render_template('404.html'), 404
Above markdown page URL is simple, since it is a seperate UI. But if I goes to DeviceView/AccountView/AccessView for list/show/add/edit operations. I realized that I need a unique styles of UI.
So, now how can I reuse the existing templates/widgets of FAB with custom sqlalchemy queries? Here is my code for DeviceView.
class DeviceView(ModelView):
datamodel = SQLAInterface(Device)
related_views = [EventView, AccessView]
show_template = 'appbuilder/general/model/show_cascade.html'
edit_template = 'appbuilder/general/model/edit_cascade.html'
#expose('/host')
#has_access
def host(self):
base_filters = [['name', FilterStartsWith, 'S'],]
#if there is not return, FAB will throw error
return "host view:{}".format(repr(base_filters))
#expose('/my')
#has_access
def my(self):
# A pure testing method
rec = db.session.query(Access).filter_by(id = 1).all()
if rec:
for r in rec:
print "rec, acc:{}, dev:{}, host:{}".format(r.account_id, r.device_id, r.is_host)
return self.render_template('list.html', title = "My Accesses", body = "{}".format(repr(r)))
else:
return repr(None)
Besides sqlalchemy code with render_template(), I guess base_filters can also help to define custom queries, however, I have no idea how to get query result and get them rendered.
Please give me some reference code or example if possible. Actually I have grep keywords of "db.session/render_template/expoaw"in FAB's github sources. But no luck.

calling a function to obtain a model field value

I'm trying to get a unique value for a field (unique within the db column).
my code (other model fields omitted):
class PlatformUserChildren(models.Model):
dashboard = models.CharField('dashboard URL', max_length=64, unique=True, default=createDashboardCode(self))
def createDashboardCode(self):
stringCheck = False
while stringCheck is False:
newString = str(uuid.uuid4())[:32]
doesStringExist = newString in self.dashboard
if not doesStringExist:
stringCheck = True
return newString
I'm getting name 'self' is not defined as an error.
What should I be passing to the function so that I can check the db column to ensure the value is unique -or- is there a built-in way of doing this?
What I've already tried or looked at:
setting unique=True for the field and using default=uuid.uuid4 - that gives me duplicate values and generates a validation error (SO link)
I'm aware of Django 1.8's UUID field, however i'm on 1.7
The problem lies in the following line (indented for better readability) as you already know and mentioned before:
dashboard = models.CharField(
'dashboard URL',
max_length=64,
unique=True,
default=createDashboardCode(self)
)
In this part:
default=createDashboardCode(self)
you're calling the method createDashboardCode with the argument self. This is wrong, because you never pass self to a method as it is passed by Python. Whenever you call the method createDashboardCode you should do it this way:
createDashboardCode()
That's it, you're not passing the argument self explicitly.
You're getting an error "name 'self' is not defined" because self is not defined. There is no variable self in your code that you can pass to the method.
Now we're one step further, but your problem won't be solved if you just apply this slight change to your code.
The return value from the method createDashboardCode will be assigned to default. That's not what you really want. You have to assign a reference of the method to default:
default = createDashboardCode
Pay attention to the missing brackets. This will call the method every time a new instance of the model is created
Define a function:
def my_function():
print "hello"
and run it in the Python interpreter:
# once like this
my_function()
# and again like this
my_function
and you'll see the difference. That should help you to better comprehend this issue.

Assigning values to a query result already set up with a foreign key

I have a database of exhibition listings related by foreign key to a database of venues where they take place. Django templates access the venue information in the query results through listing.venue.name, listing.venue.url, and so on.
However, some exhibitions take place in temporary venues, and that information is stored in the same database, in what would be listing.temp_venue_url and such. Because it seems wasteful and sad to put conditionals all over the templates, I want to move the info for temporary venues to where the templates are expecting info for regular venues. This didn't work:
def transfer_temp_values(listings):
for listing in listings:
if listing.temp_venue:
listing.venue = Venue
listing.venue.name = listing.temp_venue
listing.venue.url = listing.temp_venue_url
listing.venue.state = listing.temp_venue_state
listing.venue.location = listing.temp_venue_location
The error surprised me:
ValueError at /[...]/
Cannot assign "<class 'myproject.gsa.models.Venue'>": "Exhibition.venue" must be a "Venue" instance.
I rather thought it was. How do I go about accomplishing this?
The error message is because you have assigned the class Venue to the listing, rather than an instance of it. You need to call the class to get an instance:
listing.venue = Venue()