Hello,
Here's my simple model (For simplification, consider Face as a Billboard) :
+-----+ +-----------+ +----------+
|Face |<...... |Campaign | ...>|TimePeriod|
+-----+ . +-----------+ . +----------+
|code | . |time_period|.... |start_time|
+-----+ . +-----------+ +----------+
....|faces | |end_time |
+-----------+ +----------+
One way to read this model is : A campaign can book multiple faces during a
certain period of time.
What I want to do is get all the available Faces for a given period of time, to
see what faces can I book for a new campaign that longs for that particular
period of time. I would typically have a Face.get_available(time_period) class
method that does the job. This method would look for all the faces that don't
have an ongoing booking. My question is : how to write such a method ?
Here's how I figured it out (couldn't get it to work) :
class Face(Entity):
using_options(tablename="faces")
@classmethod
def get_available(self,time_period):
"""
Return faces that are not booked (not in any campaign) during the given
time_period.
"""
# start_date_cond = TimePeriod.start_date > time_period.end_date
# end_date_cond = TimePeriod.end_date < time_period.start_date
# available_periods =
Campaign.time_period.has(or_(start_date_cond,end_date_cond))
# unavailable_periods = not(available_periods)
# I am pretty sure that the time conditions are good.
# Here's a good way to convince yourself (read from bottom to top) :
# L1
0-----------------------------------------------
# L2 ------------------------------------------|
# L3
0--------------------[--------------------]-------------------------->
# L3 represents the desired period (passed as argument) going from "["
to "]"
# place the start date of the booked face anywhere on L2
# place the end date of the booked face anywhere on L1
# of course, end date must be after start date...
# Anyway you do it, your face isn't available for the period of time in
L3.
start_date_cond = TimePeriod.start_date <= time_period.end_date
end_date_cond = TimePeriod.end_date >= time_period.start_date
unavailable_periods =
Campaign.time_period.has(and_(start_date_cond,end_date_cond))
# I am not sure about what follows...
filter_cond = not_(unavailable_periods)
join_clause = Campaign.faces
return Face.query.filter(filter_cond).outerjoin(join_clause).all()
This code returns only faces that have already been booked before or have a
future booking, and are free for the moment. But faces with no bookings at all
are not returned. This may be due to an incorrect outerjoin ? (I also tried a
simple join with no success)
Here's the generated sql for one query :
2010-10-12 19:34:22,837 INFO sqlalchemy.engine.base.Engine.0x...f4ec
SELECT faces.id AS faces_id
FROM campaigns LEFT OUTER JOIN campaigns_faces__faces AS
campaigns_faces__faces_1 ON campaigns.id =
campaigns_faces__faces_1.campaigns_id
LEFT OUTER JOIN faces ON faces.id = campaigns_faces__faces_1.faces_id
WHERE NOT (EXISTS (SELECT 1
FROM time_periods
WHERE campaigns.time_period_id = time_periods.id
AND time_periods.start_date <= %(start_date_1)s
AND time_periods.end_date >= %(end_date_1)s))
2010-10-12 19:34:22,837 INFO sqlalchemy.engine.base.Engine.0x...f4ec
{'start_date_1': datetime.date(2010, 10, 30), 'end_date_1': datetime.date(2010,
10, 20)}
[<Face id=1 at 0x932218c >]
Any help would be very appreciated.
Y.Chaouche
PS : and please, don't give me that lame "it's elixir" excuse. The question is
about how to construct the proper query for the desired operation in a
sqlalchemy way. Elixir is only another Declarative approach + mapping, that's
it.
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en.
# ORM
from sqlalchemy.sql.expression import *
from sqlalchemy.orm import reconstructor
from elixir import *
# stdlib
from datetime import date
import sys
class TimePeriod(Entity):
"""
"""
using_options(tablename="time_periods")
start_date = Field(Date())
end_date = Field(Date())
name = Field(Unicode())
class Campaign(Entity):
using_options(tablename="campaigns")
time_period = ManyToOne("TimePeriod")
faces = ManyToMany("Face")
class Face(Entity):
using_options(tablename="faces")
@classmethod
def get_available_faces(self,time_period):
"""
Return faces that are not booked during the given time_period.
"""
# start_date_cond = TimePeriod.start_date > time_period.end_date
# end_date_cond = TimePeriod.end_date < time_period.start_date
# available_periods = Campaign.time_period.has(or_(start_date_cond,end_date_cond))
start_date_cond = TimePeriod.start_date <= time_period.end_date
end_date_cond = TimePeriod.end_date >= time_period.start_date
unavailable_periods = Campaign.time_period.has(and_(start_date_cond,end_date_cond))
filter_cond = not_(unavailable_periods)
join_clause = Campaign.faces
return Face.query.filter(filter_cond).outerjoin(join_clause).all()
class DB:
def __init__(self,echo=False):
self.metadata = metadata
self.metadata.bind = "postgresql engine's url"
self.metadata.bind.echo = echo
self.session = session
self.session.bind = metadata.bind
# Use elixir's entities collection
setup_all()
self.drop_all()
create_all()
self.metadata.create_all()
self.session.commit()
def drop_all(self):
"""
"""
for table_name in metadata.tables.keys():
self.session.execute("DROP TABLE IF EXISTS %s CASCADE" % table_name)
self.commit()
def commit(self):
self.session.commit()
def flush(self):
self.session.flush()
echo = len(sys.argv) > 1 and sys.argv[1] != "False" or False
db = DB(echo=echo)
start_date1 = date(year=2010,month=10,day=1)
end_date1 = date(year=2010,month=10,day=15)
start_date2 = date(year=2010,month=10,day=10)
end_date2 = date(year=2010,month=10,day=19)
start_date3 = date(year=2010,month=10,day=20)
end_date3 = date(year=2010,month=10,day=30)
face1 = Face()
face2 = Face()
face3 = Face()
tp1 = TimePeriod(start_date = start_date1,end_date = end_date1)
tp2 = TimePeriod(start_date = start_date2,end_date = end_date2)
tp3 = TimePeriod(start_date = start_date3,end_date = end_date3)
campaign1 = Campaign(time_period=tp1)
campaign1.faces.append(face1)
db.commit()
print Face.query.all()
print Campaign.query.all()
print TimePeriod.query.all()
print "*"*40
print Face.get_available_faces(tp2)
print Face.get_available_faces(tp3)