Skip to content

Commit

Permalink
DEV ONLY Issue to join data from other table in hybrid expression in …
Browse files Browse the repository at this point in the history
…CourseEvent
  • Loading branch information
Tschuppi81 committed Nov 27, 2023
1 parent eae5dcd commit e6fcdb1
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 40 deletions.
1 change: 1 addition & 0 deletions src/onegov/ballot/models/election/election.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def counted(self) -> bool:

return (sum(1 for r in self.results if r.counted) == count)

# tschupre example for expression
@counted.expression # type:ignore[no-redef]
def counted(cls) -> 'ColumnElement[bool]':
expr = select([
Expand Down
3 changes: 3 additions & 0 deletions src/onegov/core/orm/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,9 @@ def ensure_schema_exists(self, schema: str) -> None:
base.metadata.create_all(conn)

declared_classes.update(base._decl_class_registry.values())
# declared_classes.update(
# base.registry._class_registry.values()) # upgrading
# to sqlalchemy 1.4.x

conn.execute('COMMIT')
finally:
Expand Down
16 changes: 14 additions & 2 deletions src/onegov/directory/models/directory_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ class DirectoryEntry(Base, ContentMixin, CoordinatesMixin, TimestampMixin,
__tablename__ = 'directory_entries'

es_properties = {
'keywords': {'type': 'keyword'},
# 'keywords': {'type': 'keyword'}, # produce a literal issue when
# using sqlalchemy 1.4
'title': {'type': 'localized'},
'lead': {'type': 'localized'},
'_directory_id': {'type': 'keyword'},

# since the searchable text might include html, we remove it
# even if there's no html -> possibly decreasing the search
# quality a bit
'text': {'type': 'localized_html'}
# 'text': {'type': 'localized_html'}
}

@property
Expand Down Expand Up @@ -103,14 +104,25 @@ def keywords(self):
def keywords(self, value):
self._keywords = {k: '' for k in value} if value else None

# @property
@hybrid_property
def text(self):
return self.directory.configuration.extract_searchable(self.values)

# @text.expression
# def text(cls):
# expr = ''
# print(f'*** hybrid prop text: {expr}')
# return expr

@property
def values(self):
return self.content and self.content.get('values', {})

@hybrid_property
def values(cls):
return cls.content['values']

@values.setter
def values(self, values):
self.content = self.content or {}
Expand Down
27 changes: 26 additions & 1 deletion src/onegov/feriennet/models/activity.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from functools import cached_property

from sqlalchemy import func, text, select, alias, Column, and_
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Query, column_property

from onegov.activity import Activity, ActivityCollection, Occasion
from onegov.activity import PublicationRequestCollection
Expand All @@ -12,6 +14,7 @@
from onegov.org.models.ticket import OrgTicketMixin
from onegov.search import SearchableContent
from onegov.ticket import handlers, Handler, Ticket
from onegov.user import User


class VacationActivity(Activity, CoordinatesExtension, SearchableContent):
Expand All @@ -24,7 +27,7 @@ class VacationActivity(Activity, CoordinatesExtension, SearchableContent):
'title': {'type': 'localized'},
'lead': {'type': 'localized'},
'text': {'type': 'localized_html'},
'organiser': {'type': 'text'}
# 'organiser': {'type': 'text'}
}

@property
Expand All @@ -39,8 +42,18 @@ def es_public(self):
def es_skip(self):
return self.state == 'preview'

@hybrid_property
def lead(self):
return self.meta['lead'].astext

@hybrid_property
def text(self):
return self.content['text'].astext

# @property
@hybrid_property
def organiser(self):
print(' *** tschupre property organizer()')
organiser = [
self.user.username,
self.user.realname
Expand All @@ -67,6 +80,18 @@ def organiser(self):

return organiser

@organiser.expression
def organiser(cls):
expr = (
Query(User.username, User.realname).
select_from(cls).join(User).
filter(cls.username == User.username).
filter(func.concat_ws(' ', User.username, User.realname)).
as_scalar()
)
print(expr)
return expr.label('organizer')

def ordered_tags(self, request, durations=None):
tags = [request.translate(_(tag)) for tag in self.tags]

Expand Down
43 changes: 37 additions & 6 deletions src/onegov/fsi/models/course_attendee.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

from onegov.core.orm import Base
from onegov.core.orm.types import UUID, JSON
from sqlalchemy import Boolean
from onegov.user import User
from sqlalchemy import Boolean, case, select, and_, func
from onegov.search import ORMSearchable
from sedate import utcnow
from sqlalchemy import Column, Text, ForeignKey, ARRAY, desc
from sqlalchemy.orm import relationship, object_session, backref
from uuid import uuid4


from typing import TYPE_CHECKING

if TYPE_CHECKING:
from onegov.user import User
from .course_subscription import CourseSubscription


external_attendee_org = "Externe Kursteilnehmer"


Expand Down Expand Up @@ -43,8 +43,8 @@ class CourseAttendee(Base, ORMSearchable):
'first_name': {'type': 'text'},
'last_name': {'type': 'text'},
'organisation': {'type': 'text'},
'email': {'type': 'text'},
'title': {'type': 'text'},
# 'email': {'type': 'text'},
# 'title': {'type': 'text'},
}

es_public = False
Expand Down Expand Up @@ -118,6 +118,21 @@ def title(self):
) if p
)) or self.email

@title.expression
def title(cls):
return case((
(
and_(
cls.first_name != '',
cls.last_name != ''
),
# cls.first_name + ' ' + cls.last_name
func.concat(cls.first_name + ' ' + cls.last_name)
)
),
else_=cls.email,
)

@property
def lead(self):
return self.organisation
Expand All @@ -140,6 +155,22 @@ def email(self):
return self._email
return self.user.username

@email.expression
def email(cls):
from onegov.user import User

return case((
(
# cls.user_id.isnot(None),
cls.user_id != '',
cls._email
),
), else_=(
select([User.username]).
where(cls.user_id == User.id).
label('email'))
)

@email.setter
def email(self, value):
self._email = value
Expand Down Expand Up @@ -194,7 +225,7 @@ def repeating_courses(self):

session = object_session(self)

result = session.query(CourseEvent).join(Course)\
result = session.query(CourseEvent).join(Course) \
.filter(Course.mandatory_refresh == True)

result = result.join(CourseSubscription)
Expand Down
54 changes: 49 additions & 5 deletions src/onegov/fsi/models/course_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@
from icalendar import Event as vEvent
from sedate import utcnow, to_timezone
from sqlalchemy import (
Column, Boolean, SmallInteger, Enum, Text, Interval, ForeignKey, or_, and_)
Column, Boolean, SmallInteger, Enum, Text, Interval, ForeignKey, or_, and_,
select)
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship, backref, object_session
from sqlalchemy.orm import relationship, backref, object_session, join
from uuid import uuid4

from onegov.core.mail import Attachment
from onegov.core.orm import Base
from onegov.core.orm.mixins import TimestampMixin
from onegov.core.orm.types import UUID, UTCDateTime
from onegov.fsi import _
from onegov.fsi.models.course import Course
from onegov.fsi.models.course_attendee import CourseAttendee
from onegov.fsi.models.course_subscription import CourseSubscription
from onegov.fsi.models.course_subscription import subscription_table
from onegov.search import ORMSearchable

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .course import Course
from .course_notification_template import (
Expand All @@ -40,7 +43,6 @@

# for forms...
def course_status_choices(request=None, as_dict=False):

if request:
translations = (
request.translate(v) for v in COURSE_EVENT_STATUSES_TRANSLATIONS)
Expand All @@ -54,14 +56,14 @@ def course_status_choices(request=None, as_dict=False):


class CourseEvent(Base, TimestampMixin, ORMSearchable):

default_reminder_before = datetime.timedelta(days=14)

__tablename__ = 'fsi_course_events'

es_properties = {
# expression for name and desc implementiert aber subquery issue
'name': {'type': 'localized'},
'description': {'type': 'localized'},
# 'description': {'type': 'localized'},
'location': {'type': 'localized'},
'presenter_name': {'type': 'text'},
'presenter_company': {'type': 'text'},
Expand Down Expand Up @@ -89,6 +91,41 @@ def title(self):
def name(self):
return self.course.name

@name.expression
def name(cls):
# both variants lead to the same error:
# sqlalchemy.exc.NotSupportedError: (psycopg2.errors.FeatureNotSupported) cannot use subquery in column generation expression
# subquery issue probably produced due where clause

# expr = select([Course.name])
# expr = expr.where(Course.id == cls.course_id)
# expr = expr.label('name')
# return expr

# expr = (select([Course.name]).where(Course.id == cls.course_id).
# select_from(join(cls, Course)).label('name'))

# the variant below are closer to the final version (explicit
# join) but throw: 'Can't find any foreign key relationships between
# 'Select object' and 'fsi_courses'
expr = select([Course.name]).select_from(cls).join(
Course).where(Course.id == cls.course_id).label('name')

# throws: subquery in FROM must-have an alias
# j = select([cls.course_id]).join(Course, Course.id ==
# cls.course_id).alias('j')
# expr = select([Course.name]).select_from(j).label('name')

# example https://docs.sqlalchemy.org/en/20/orm/queryguide/select.html#joining-to-subqueries
# throwing: TypeError: 'DeclarativeMeta' object is not iterable
# subq = select(Address).where(Address.email_address == "pat999@aol.com").subquery()
# stmt = select(User).join(subq, User.id == subq.c.user_id)
# subquery = select(cls).where(cls.course_id == Course.id).subquery()
# expr = select(Course).join(subquery, Course.id == subquery.course_id)

print(expr)
return expr

@property
def lead(self):
return (
Expand All @@ -101,6 +138,13 @@ def lead(self):
def description(self):
return self.course.description

@description.expression
def description(cls):
expr = select([Course.description])
expr = expr.where(Course.id == cls.course_id)
expr = expr.label('description')
return expr

def __str__(self):
start = to_timezone(
self.start, 'Europe/Zurich').strftime('%d.%m.%Y %H:%M')
Expand Down
4 changes: 4 additions & 0 deletions src/onegov/org/models/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class Topic(Page, TraitInfo, SearchableContent, AccessExtension,
lead_when_child = content_property(default=True)

@hybrid_property
def lead(self): # noqa: F811
return self.content['lead']

@lead.expression
def lead(self): # noqa: F811
return self.content['lead'].astext

Expand Down
Loading

0 comments on commit e6fcdb1

Please sign in to comment.