I'm fairly new to peewee, but have some strong background on SQLAlchemy (and all the vices that come with it). I'm trying to create a custom hybrid expression that correlates to a third (or even N) table. I'll try to demonstrate in an example (non-tested) code:
class BaseModel(Model):
class Meta:
database = database
class Person(BaseModel):
id = PrimaryKeyField(column_name="person_id")
name = CharField(max_length=255, column_name="person_name")
username = CharField(max_length=255, column_name="person_username")
class PersonTree(BaseModel):
id = PrimaryKeyField(column_name="person_tree_id")
name = CharField(max_length=255, column_name="person_tree_name")
code = CharField(max_length=255, column_name="person_tree_code")
person = ForeignKeyField(
column_name="person_id",
model=Person,
field="id",
backref="tree",
)
class Article(BaseModel):
id = PrimaryKeyField(column_name="article_id")
name = CharField(max_length=255, column_name="article_name")
branch = ForeignKeyField(
column_name="person_tree_id",
model=PersonTree,
field="id",
backref="articles",
)
#hybrid_property
def username(self):
"""
This gives me the possibility to grab the direct username of an article
"""
return self.branch.person.username
#username.expression
def username(cls):
"""
What if I wanted to do: Article.query().where(Article.username == "john_doe") ?
"""
pass
With the username hybrid_property on Article, I can get the username of the Person related to an Article using the PersonTree as a correlation, so far so good, but ... What if I wanted to "create a shortcut" to query all Articles created by the "john_doe" Person username, without declaring the JOINs every time I make the query and without relying on .filter(branch__person__username="john_doe")? I know it's possible with SA (to a great extent), but I'm finding this hard to accomplish with peewee.
Just for clarification, here's the SQL I hope to be able to construct:
SELECT
*
FROM
article a
JOIN person_tree pt ON a.person_tree_id = pt.person_tree_id
JOIN person p ON pt.person_id = p.person_id
WHERE
p.username = 'john_doe';
Thanks a lot in advance!
Hybrid properties can be used to allow an attribute to be expressed as a property of a model instance or as a scalar computation in a SQL query.
What you're trying to do, which is add multiple joins and stuff via the property, is not possible using hybrid properties.
What if I wanted to "create a shortcut" to query all Articles created by the "john_doe" Person username
Just add a normal method:
#classmethod
def by_username(cls, username):
return (Article
.select(Article, PersonTree, Person)
.join(PersonTree)
.join(Person)
.where(Person.name == username))
Context: I'm trying to query data from SQL Server via Pyodbc & use looping logic to read the results into the variables in the query block below.
Question(s): Can someone please help me modify the code block below so that it properly populates the variables via the looping logic? I suspect because I'm using the fetchall() method on the query cursor that each result row transforms into a tuple within a list -- which then seems to make the looping logic below it useless. Can someone please suggest an alternative solution?
from constantcontact import ConstantContact
from constantcontact import Contact
import requests
import json
import pyodbc
username = 'REDACTED'
password = 'REDACTED'
sample_contact_connection = pyodbc.connect("Driver={ODBC Driver 13 for SQL Server};""Server=PC;""Database=leadgen_sandbox;""Username="+username+";""Password="+password+";""Trusted_Connection=yes;")
sample_contact_cursor = sample_contact_connection.cursor()
sample_contact_query = "SELECT first_name,last_name,title,company_name,email_address FROM leadgen_sandbox.dbo.sample_contacts"
sample_contact_connection.autocommit = True
sample_contact_cursor.execute(sample_contact_query)
print(sample_contact_cursor.fetchall())
constantcontact = ConstantContact('REDACTED','REDACTED')
list_id = '1816761971'
for [first_name, last_name, title, company_name, email_address] in sample_contact_cursor.fetchall():
new_contact = Contact()
new_contact.set_first_name(''+first_name+'')
new_contact.set_last_name(''+last_name+'')
new_contact.set_job_title(''+title+'')
new_contact.set_company_name(''+company_name+'')
new_contact.add_list_id(''+list_id+'')
new_contact.set_email_address(''+email_address+'')
response = constantcontact.post_contacts(new_contact)
response_text = json.dumps(response, indent = 4, sort_keys = True)
print(response_text)
sample_contact_connection.close()
When you called sample_contact_cursor.fetchall() , you already exhausted the contents of the cursor. So, it's content would no longer be available for the loop. Removing the print before the loop would fix this. Also, the .fetchall() is redundant in the loop as each row would be read in the for loop one by one anyways. You could write this way as well:
for [first_name, last_name, title, company_name, email_address] in sample_contact_cursor:
new_contact = Contact()
new_contact.set_first_name(''+first_name+'')
new_contact.set_last_name(''+last_name+'')
#Write your remaining code
If you do need to first print the cursor, and then run the loop, you have to execute the cursor once agin before the loop like this:
sample_contact_cursor.execute(sample_contact_query)
print(sample_contact_cursor.fetchall())
constantcontact = ConstantContact('REDACTED','REDACTED')
list_id = '1816761971'
sample_contact_cursor.execute(sample_contact_query)
for [first_name, last_name, title, company_name, email_address] in sample_contact_cursor:
new_contact = Contact()
new_contact.set_first_name(''+first_name+'')
new_contact.set_last_name(''+last_name+'')
#Write your remaining code
EDIT: This is very similiar to SqlAlchemy - Filtering by Relationship Attribute in that we are both trying to filter on relationship attributes. However, they are filtering on matching an exact value, whereas I am filtering by using like/contains. Because of this, as pointed out in the comments, my solution requires an extra step that was not apparent in the other post.
Let me preface this with: I'm still quite new to SQLAlchemy, so it's entirely possible I'm going about this in the exact wrong way.
I have a Flask API that has defined "alerts" and "events". An alert can only belong to a single event, and an event can have multiple alerts. The schema for the alerts is as follows:
class Alert(PaginatedAPIMixin, db.Model):
__tablename__ = 'alert'
id = db.Column(db.Integer, primary_key=True, nullable=False)
type = db.relationship('AlertType')
type_id = db.Column(db.Integer, db.ForeignKey('alert_type.id'), nullable=False)
url = db.Column(db.String(512), unique=True, nullable=False)
event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False)
event = db.relationship('Event')
def __str__(self):
return str(self.url)
def to_dict(self):
return {'id': self.id,
'event': self.event.name,
'type': self.type.value,
'url': self.url}
One of the API endpoints lets me get a list of alerts based on various filter criteria. For example, get all alerts of alert_type X, or get all alerts with X inside their alert_url. However, the one that is stumping me is that I want to be able to get all alerts with X inside their associated event name.
Here is the API endpoint function (the commented out events bit is my initial "naive" approach that does not work due to event being a relationship), but you can get the idea what I'm trying to do with the filtering.
def read_alerts():
""" Gets a list of all the alerts. """
filters = set()
# Event filter
#if 'event' in request.args:
# filters.add(Alert.event.name.like('%{}%'.format(request.args.get('event'))))
# URL filter
if 'url' in request.args:
filters.add(Alert.url.like('%{}%'.format(request.args.get('url'))))
# Type filter
if 'type' in request.args:
type_ = AlertType.query.filter_by(value=request.args.get('type')).first()
if type_:
type_id = type_.id
else:
type_id = -1
filters.add(Alert.type_id == type_id)
data = Alert.to_collection_dict(Alert.query.filter(*filters), 'api.read_alerts', **request.args)
return jsonify(data)
The filters set that is built gets fed to the to_collection_dict() function, which essentially returns a paginated list of the query with all of the filters.
def to_collection_dict(query, endpoint, **kwargs):
""" Returns a paginated dictionary of a query. """
# Create a copy of the request arguments so that we can modify them.
args = kwargs.copy()
# Read the page and per_page values or use the defaults.
page = int(args.get('page', 1))
per_page = min(int(args.get('per_page', 10)), 100)
# Now that we have the page and per_page values, remove them
# from the arguments so that the url_for function does not
# receive duplicates of them.
try:
del args['page']
except KeyError:
pass
try:
del args['per_page']
except KeyError:
pass
# Paginate the query.
resources = query.paginate(page, per_page, False)
# Generate the response dictionary.
data = {
'items': [item.to_dict() for item in resources.items],
'_meta': {
'page': page,
'per_page': per_page,
'total_pages': resources.pages,
'total_items': resources.total
},
'_links': {
'self': url_for(endpoint, page=page, per_page=per_page, **args),
'next': url_for(endpoint, page=page + 1, per_page=per_page, **args) if resources.has_next else None,
'prev': url_for(endpoint, page=page - 1, per_page=per_page, **args) if resources.has_prev else None
}
}
return data
I understand that I can get the filtered list of alerts by their associated event name doing something along these lines with options and contains_eager:
alerts = db.session.query(Alert).join(Alert.event).options(contains_eager(Alert.event)).filter(Event.name.like('%{}%'.format(request.args.get('event')))).all()
But I have not gotten something similar to that to work when added to the filters set.
I created a helper function to update customer data in a class. When I call the function it updates all the information. If I try it again on my tests... it creates a new entry into the customer database, creating a duplicate..
I cant figure out if its my logic or what I am doing wrong. Can someone assist.
I am using the HTTP requests package
Initiate class
self.customer = Sq_Customer(
first_name = 'Testy',
last_name = 'McTesty',
email = 'McTesty#testy.com',
phone= '123-456-7890'
)
Update user JSON and Function call
self.data = {
'given_name': 'Dummy',
'email_address': dummy_account#testing.com',
'address': {
'address_line_1': '1234 Main Street',
'address_line_2': '',
'locality': 'New York',
'administrative_district_level_1': 'NY',
'postal_code': '11413',
'country': 'US'
}
}
self.update_customer = self.customer.update_customer_acct(Customer ID, self.data)
My Helper Function
def update_customer_acct(self, user_id, data):
'''
Update Customer information.
'''
self.customer = self.get_customer(user_id)
if self.customer['customer']['id'] == user_id:
self.update_customer_data = self.connect.put('/v2/customers/' + user_id, data)
return self.__sqware_json_decoder(self.update_customer_data)
else:
return '{}'.format('There is no account associated with that ID.')
I removed the email and there was no longer a duplicate. It's an issue with me creating a customer during the test. When the email is changed it creates a new account. Thanks again.
I do a project about Timesheet in OpenERP. I have this problem:
this is x_luong table.
class x_luong(osv.osv):
_name = 'x_luong'
_description = 'Luong'
_columns = {'name': fields.many2one('x_nhanvien', 'Mã nhân viên', size=10, required='1'),
'ma_luong': fields.integer('ma luong', size=10, required='1'),
'giolam': fields.float('Giờ làm', size=100, required='1'),
'giolamthuc': fields.char('Gio lam thuc te', size=5, required='1'),
'time_in': fields.char('Gio vào', size=20),
'time_out' :fields.char('Gio về', size=20),
'state' :fields.selection([('dangnhap','Đẳng nhập.'),('rave','Ra về')]),
'test': fields.integer('Kiem tra', size=20),
'phutvao': fields.integer('Phut vao ', size=20),
'phutra': fields.integer('phut ra', size=20),
}
_defaults = {'state':'dangnhap',
}
and this some function in it:
this 2 function mean get time when the staff sign_in or sign_out the system:
def get_timein(self,cr,uid,ids,context={}):
obj = self.browse(cr,uid,ids,context=context)[0]
timein = str(datetime.now())
self.write(cr, uid, ids, {'time_in':timein }, context=context)
return 1
def get_timeout(self,cr,uid,ids,context={}):
obj = self.browse(cr,uid,ids,context=context)[0]
timeout = str(datetime.now())
self.write(cr, uid, ids, {'time_out':timeout }, context=context)
return 1
and this 2 function for button sign_in and sign_out:
def cho_dangnhap(self,cr,uid,ids,context={}):
self.pool.get('x_luong').write(cr,uid,ids,{'state':'dangnhap'})
self.get_timein(cr,uid,ids)
return 1
def cho_rave(self,cr,uid,ids,context={}):
self.pool.get('x_luong').write(cr,uid,ids,{'state':'rave'})
self.get_timeout(cr,uid,ids)
self.tinh_thoigian(cr,uid,ids)
self.insert(cr,uid,ids)
function tinh_thoigian mean cut the string time for get ... hour or min for calculation
def _thoigianlam(self,cr,uid,ids,context={}):
obj = self.browse(cr,uid,ids,context=context)[0]
hour_den = int(obj.time_in[12:13])
hour_di = int(obj.time_out[12:13])
min_den = int(obj.time_in[15:16])
min_di = int(obj.time_out[15:16])
gl = int(hour_di)-int(hour_den)
pl = min_di-min_den
thucte = str(gl)+':'+pl
self.write(cr, uid, ids, {'giolam':gl }, context=context)
self.write(cr, uid, ids, {'giolamthuc':thucte }, context=context)
return 1
and last function insert() get ma_luong(i think this same the primary key in sql) and giolam(the hour of the staff work in company), time_in, time_out and this is function insert()
def insert(self,cr,uid,ids,context={}):
obj = self.browse(cr,uid,ids,context=context)
values = {'ma_luong':obj.name.id,
'giolam':obj.giolam,
'time_in':time_in,
'time_out':time_out,
self.pool.get('x_giolam').create(cr,uid,values,context=context)
with this function i want insert data in table x_giolam because when the staff sign in or sign out the system in day ... the data of it with save in this table and a other day when they do it again it with save it again ... and last month if you want calculation about salary of them you just select ma_luong=ma_luong(of table x_luong) and this table x_giolam:
class x_giolam(osv.osv):
_name = 'x_giolam'
_description = 'Gio Lam'
_columns = {'name': fields.integer('Lọai',size=64,required="true"),
'giolam' : fields.float('Gio lam',size=64,required="True"),
'time_in': fields.char('Gio vào',size=20),
'time_out' :fields.char('Gio về',size=20),
}
and i have 3 question with my project:
1) function insert have aerror:
AttributeError: 'browse_record_list' object has no attribute 'name'
How can i fix it ??? i data of it is save in table x_giolam
2) how can i select many row of table x_giolam which of thte employee' own.. give me some example about this function
3) how i can organization field.Xml when i show rows in
Sorry for your troubles because it is so long ... but i hope every body in here can help me. Python and open Erp so difference with c++ or c#. And this my project"research and write a module timesheet with OpenErp" of me and next week is deadline.
English of me not good, i'm sory about it!!!
Thanks!!
I can help with your first question. The problem is in this code:
def insert(self,cr,uid,ids,context={}):
obj=self.browse(cr,uid,ids,context=context)
values={'ma_luong':obj.name.id,
The error message was like this:
AttributeError: 'browse_record_list' object has no attribute 'name'
If you call orm.browse() with a list of ids, you will get back a list of browse records. You then have to enumerate through the list, or get a single entry from the list to work with.
For example:
for luong in self.browse(cr,uid,ids,context=context):
print luong.name
Or:
luongs = self.browse(cr,uid,ids,context=context)
luong = luongs[0]
print luong.name
Why don't you take a look at the standard hr_attendance module and go on from there?
For your model, the name is a reserved field name, so it would be best to keep it as achar. Try that change and see if it solves your error message.
For the other two questions, I think you should try to rephrase them a little better...
The type of obj is list of records, so for browse the list of records, you must define a one element.
in your case, you can type : obj[0].giolam --> for the giolam of the first record of obj.
forgive me for my bad english