1 | from sqlalchemy.sql.expression import func |
---|
2 | # Cannot import galaxy.model b/c it creates a circular import graph. |
---|
3 | import galaxy |
---|
4 | import logging |
---|
5 | log = logging.getLogger( __name__ ) |
---|
6 | |
---|
7 | class RuntimeException( Exception ): |
---|
8 | pass |
---|
9 | |
---|
10 | class UsesItemRatings: |
---|
11 | """ |
---|
12 | Mixin for getting and setting item ratings. |
---|
13 | |
---|
14 | Class makes two assumptions: |
---|
15 | (1) item-rating association table is named <item_class>RatingAssocation |
---|
16 | (2) item-rating association table has a column with a foreign key referencing |
---|
17 | item table that contains the item's id. |
---|
18 | """ |
---|
19 | def get_ave_item_rating_data( self, db_session, item, webapp_model=None ): |
---|
20 | """ Returns the average rating for an item.""" |
---|
21 | if webapp_model is None: |
---|
22 | webapp_model = galaxy.model |
---|
23 | item_rating_assoc_class = self._get_item_rating_assoc_class( item, webapp_model=webapp_model ) |
---|
24 | if not item_rating_assoc_class: |
---|
25 | raise RuntimeException( "Item does not have ratings: %s" % item.__class__.__name__ ) |
---|
26 | item_id_filter = self._get_item_id_filter_str( item, item_rating_assoc_class ) |
---|
27 | ave_rating = db_session.query( func.avg( item_rating_assoc_class.rating ) ).filter( item_id_filter ).scalar() |
---|
28 | # Convert ave_rating to float; note: if there are no item ratings, ave rating is None. |
---|
29 | if ave_rating: |
---|
30 | ave_rating = float( ave_rating ) |
---|
31 | else: |
---|
32 | ave_rating = 0 |
---|
33 | num_ratings = int( db_session.query( func.count( item_rating_assoc_class.rating ) ).filter( item_id_filter ).scalar() ) |
---|
34 | return ( ave_rating, num_ratings ) |
---|
35 | |
---|
36 | def rate_item( self, db_session, user, item, rating, webapp_model=None ): |
---|
37 | """ Rate an item. Return type is <item_class>RatingAssociation. """ |
---|
38 | if webapp_model is None: |
---|
39 | webapp_model = galaxy.model |
---|
40 | item_rating = self.get_user_item_rating( db_session, user, item, webapp_model=webapp_model ) |
---|
41 | if not item_rating: |
---|
42 | # User has not yet rated item; create rating. |
---|
43 | item_rating_assoc_class = self._get_item_rating_assoc_class( item, webapp_model=webapp_model ) |
---|
44 | item_rating = item_rating_assoc_class() |
---|
45 | item_rating.user = user |
---|
46 | item_rating.set_item( item ) |
---|
47 | item_rating.rating = rating |
---|
48 | db_session.add( item_rating ) |
---|
49 | db_session.flush() |
---|
50 | elif item_rating.rating != rating: |
---|
51 | # User has rated item; update rating. |
---|
52 | item_rating.rating = rating |
---|
53 | db_session.flush() |
---|
54 | return item_rating |
---|
55 | |
---|
56 | def get_user_item_rating( self, db_session, user, item, webapp_model=None ): |
---|
57 | """ Returns user's rating for an item. Return type is <item_class>RatingAssociation. """ |
---|
58 | if webapp_model is None: |
---|
59 | webapp_model = galaxy.model |
---|
60 | item_rating_assoc_class = self._get_item_rating_assoc_class( item, webapp_model=webapp_model ) |
---|
61 | if not item_rating_assoc_class: |
---|
62 | raise RuntimeException( "Item does not have ratings: %s" % item.__class__.__name__ ) |
---|
63 | |
---|
64 | # Query rating table by user and item id. |
---|
65 | item_id_filter = self._get_item_id_filter_str( item, item_rating_assoc_class ) |
---|
66 | return db_session.query( item_rating_assoc_class ).filter_by( user=user ).filter( item_id_filter ).first() |
---|
67 | |
---|
68 | def _get_item_rating_assoc_class( self, item, webapp_model=None ): |
---|
69 | """ Returns an item's item-rating association class. """ |
---|
70 | if webapp_model is None: |
---|
71 | webapp_model = galaxy.model |
---|
72 | item_rating_assoc_class = '%sRatingAssociation' % item.__class__.__name__ |
---|
73 | return getattr( webapp_model, item_rating_assoc_class, None ) |
---|
74 | |
---|
75 | def _get_item_id_filter_str( self, item, item_rating_assoc_class, webapp_model=None ): |
---|
76 | # Get foreign key in item-rating association table that references item table. |
---|
77 | if webapp_model is None: |
---|
78 | webapp_model = galaxy.model |
---|
79 | item_fk = None |
---|
80 | for fk in item_rating_assoc_class.table.foreign_keys: |
---|
81 | if fk.references( item.table ): |
---|
82 | item_fk = fk |
---|
83 | break |
---|
84 | |
---|
85 | if not item_fk: |
---|
86 | raise RuntimeException( "Cannot find item id column in item-rating association table: %s, %s" % item_rating_assoc_class.__name__, item_rating_assoc_class.table.name ) |
---|
87 | |
---|
88 | # TODO: can we provide a better filter than a raw string? |
---|
89 | return "%s=%i" % ( item_fk.parent.name, item.id ) |
---|
90 | |
---|
91 | class UsesAnnotations: |
---|
92 | """ Mixin for getting and setting item annotations. """ |
---|
93 | def get_item_annotation_str( self, db_session, user, item ): |
---|
94 | """ Returns a user's annotation string for an item. """ |
---|
95 | annotation_obj = self.get_item_annotation_obj( db_session, user, item ) |
---|
96 | if annotation_obj: |
---|
97 | return annotation_obj.annotation |
---|
98 | return None |
---|
99 | |
---|
100 | def get_item_annotation_obj( self, db_session, user, item ): |
---|
101 | """ Returns a user's annotation object for an item. """ |
---|
102 | # Get annotation association class. |
---|
103 | annotation_assoc_class = self._get_annotation_assoc_class( item ) |
---|
104 | if not annotation_assoc_class: |
---|
105 | return None |
---|
106 | |
---|
107 | # Get annotation association object. |
---|
108 | annotation_assoc = db_session.query( annotation_assoc_class ).filter_by( user=user ) |
---|
109 | |
---|
110 | # TODO: use filtering like that in _get_item_id_filter_str() |
---|
111 | if item.__class__ == galaxy.model.History: |
---|
112 | annotation_assoc = annotation_assoc.filter_by( history=item ) |
---|
113 | elif item.__class__ == galaxy.model.HistoryDatasetAssociation: |
---|
114 | annotation_assoc = annotation_assoc.filter_by( hda=item ) |
---|
115 | elif item.__class__ == galaxy.model.StoredWorkflow: |
---|
116 | annotation_assoc = annotation_assoc.filter_by( stored_workflow=item ) |
---|
117 | elif item.__class__ == galaxy.model.WorkflowStep: |
---|
118 | annotation_assoc = annotation_assoc.filter_by( workflow_step=item ) |
---|
119 | elif item.__class__ == galaxy.model.Page: |
---|
120 | annotation_assoc = annotation_assoc.filter_by( page=item ) |
---|
121 | elif item.__class__ == galaxy.model.Visualization: |
---|
122 | annotation_assoc = annotation_assoc.filter_by( visualization=item ) |
---|
123 | return annotation_assoc.first() |
---|
124 | |
---|
125 | def add_item_annotation( self, db_session, user, item, annotation ): |
---|
126 | """ Add or update an item's annotation; a user can only have a single annotation for an item. """ |
---|
127 | # Get/create annotation association object. |
---|
128 | annotation_assoc = self.get_item_annotation_obj( db_session, user, item ) |
---|
129 | if not annotation_assoc: |
---|
130 | annotation_assoc_class = self._get_annotation_assoc_class( item ) |
---|
131 | if not annotation_assoc_class: |
---|
132 | return None |
---|
133 | annotation_assoc = annotation_assoc_class() |
---|
134 | item.annotations.append( annotation_assoc ) |
---|
135 | annotation_assoc.user = user |
---|
136 | # Set annotation. |
---|
137 | annotation_assoc.annotation = annotation |
---|
138 | return annotation_assoc |
---|
139 | |
---|
140 | def copy_item_annotation( self, db_session, source_user, source_item, target_user, target_item ): |
---|
141 | """ Copy an annotation from a user/item source to a user/item target. """ |
---|
142 | if source_user and target_user: |
---|
143 | annotation_str = self.get_item_annotation_str( db_session, source_user, source_item ) |
---|
144 | if annotation_str: |
---|
145 | annotation = self.add_item_annotation( db_session, target_user, target_item, annotation_str ) |
---|
146 | return annotation |
---|
147 | return None |
---|
148 | |
---|
149 | def _get_annotation_assoc_class( self, item ): |
---|
150 | """ Returns an item's item-annotation association class. """ |
---|
151 | class_name = '%sAnnotationAssociation' % item.__class__.__name__ |
---|
152 | return getattr( galaxy.model, class_name, None ) |
---|