class AbstractReviewRating(ReviewRatingMixin, db.Model): __tablename__ = 'abstract_review_ratings' __table_args__ = (db.UniqueConstraint('review_id', 'question_id'), { 'schema': 'event_abstracts' }) question_class = AbstractReviewQuestion review_class = AbstractReview
class PaperReviewRating(ReviewRatingMixin, db.Model): __tablename__ = 'review_ratings' __table_args__ = (db.UniqueConstraint('review_id', 'question_id'), { 'schema': 'event_paper_reviewing' }) question_class = PaperReviewQuestion review_class = PaperReview
class EventPerson(PersonMixin, db.Model): """A person inside an event, e.g. a speaker/author etc.""" __tablename__ = 'persons' __table_args__ = (db.UniqueConstraint('event_id', 'user_id'), db.CheckConstraint('email = lower(email)', 'lowercase_email'), db.Index(None, 'event_id', 'email', unique=True, postgresql_where=db.text("email != ''")), { 'schema': 'events' }) id = db.Column(db.Integer, primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey('events.events.id'), nullable=False, index=True) user_id = db.Column(db.Integer, db.ForeignKey('users.users.id'), nullable=True, index=True) first_name = db.Column(db.String, nullable=False, default='') last_name = db.Column(db.String, nullable=False) email = db.Column(db.String, nullable=False, index=True, default='') # the title of the user - you usually want the `title` property! _title = db.Column('title', PyIntEnum(UserTitle), nullable=False, default=UserTitle.none) affiliation = db.Column(db.String, nullable=False, default='') address = db.Column(db.Text, nullable=False, default='') phone = db.Column(db.String, nullable=False, default='') invited_dt = db.Column(UTCDateTime, nullable=True) is_untrusted = db.Column(db.Boolean, nullable=False, default=False) event = db.relationship('Event', lazy=True, backref=db.backref('persons', cascade='all, delete-orphan', cascade_backrefs=False, lazy='dynamic')) user = db.relationship('User', lazy=True, backref=db.backref('event_persons', cascade_backrefs=False, lazy='dynamic')) # relationship backrefs: # - abstract_links (AbstractPersonLink.person) # - contribution_links (ContributionPersonLink.person) # - event_links (EventPersonLink.person) # - session_block_links (SessionBlockPersonLink.person) # - subcontribution_links (SubContributionPersonLink.person) @locator_property def locator(self): return dict(self.event.locator, person_id=self.id) @return_ascii def __repr__(self): return format_repr(self, 'id', is_untrusted=False, _text=self.full_name) @property def principal(self): if self.user is not None: return self.user elif self.email: return EmailPrincipal(self.email) return None @classmethod def create_from_user(cls, user, event=None, is_untrusted=False): return EventPerson(user=user, event=event, first_name=user.first_name, last_name=user.last_name, email=user.email, affiliation=user.affiliation, address=user.address, phone=user.phone, is_untrusted=is_untrusted) @classmethod def for_user(cls, user, event=None, is_untrusted=False): """Return EventPerson for a matching User in Event creating if needed""" person = event.persons.filter_by(user=user).first() if event else None return person or cls.create_from_user( user, event, is_untrusted=is_untrusted) @classmethod def merge_users(cls, target, source): """Merge the EventPersons of two users. :param target: The target user of the merge :param source: The user that is being merged into `target` """ existing_persons = {ep.event_id: ep for ep in target.event_persons} for event_person in source.event_persons: existing = existing_persons.get(event_person.event_id) if existing is None: event_person.user = target else: existing.merge_person_info(event_person) db.session.delete(event_person) db.session.flush() @classmethod def link_user_by_email(cls, user): """ Links all email-based persons matching the user's email addresses with the user. :param user: A User object. """ from fossir.modules.events.models.events import Event query = (cls.query.join(EventPerson.event).filter( ~Event.is_deleted, cls.email.in_(user.all_emails), cls.user_id.is_(None))) for event_person in query: existing = (cls.query.filter_by( user_id=user.id, event_id=event_person.event_id).one_or_none()) if existing is None: event_person.user = user else: existing.merge_person_info(event_person) db.session.delete(event_person) db.session.flush() @no_autoflush def merge_person_info(self, other): from fossir.modules.events.contributions.models.persons import AuthorType for column_name in { '_title', 'affiliation', 'address', 'phone', 'first_name', 'last_name' }: value = getattr(self, column_name) or getattr(other, column_name) setattr(self, column_name, value) for event_link in other.event_links: existing_event_link = next( (link for link in self.event_links if link.event_id == event_link.event_id), None) if existing_event_link is None: event_link.person = self else: other.event_links.remove(event_link) for abstract_link in other.abstract_links: existing_abstract_link = next( (link for link in self.abstract_links if link.abstract_id == abstract_link.abstract_id), None) if existing_abstract_link is None: abstract_link.person = self else: existing_abstract_link.is_speaker |= abstract_link.is_speaker existing_abstract_link.author_type = AuthorType.get_highest( existing_abstract_link.author_type, abstract_link.author_type) other.abstract_links.remove(abstract_link) for contribution_link in other.contribution_links: existing_contribution_link = next( (link for link in self.contribution_links if link.contribution_id == contribution_link.contribution_id), None) if existing_contribution_link is None: contribution_link.person = self else: existing_contribution_link.is_speaker |= contribution_link.is_speaker existing_contribution_link.author_type = AuthorType.get_highest( existing_contribution_link.author_type, contribution_link.author_type) other.contribution_links.remove(contribution_link) for subcontribution_link in other.subcontribution_links: existing_subcontribution_link = next( (link for link in self.subcontribution_links if link.subcontribution_id == subcontribution_link.subcontribution_id), None) if existing_subcontribution_link is None: subcontribution_link.person = self else: other.subcontribution_links.remove(subcontribution_link) for session_block_link in other.session_block_links: existing_session_block_link = next( (link for link in self.session_block_links if link.session_block_id == session_block_link.session_block_id), None) if existing_session_block_link is None: session_block_link.person = self else: other.session_block_links.remove(session_block_link) db.session.flush() def has_role(self, role, obj): """Whether the person has a role in the ACL list of a given object""" principals = [ x for x in obj.acl_entries if x.has_management_role(role, explicit=True) ] return any( x for x in principals if ((self.user_id is not None and self.user_id == x.user_id) or ( self.email is not None and self.email == x.email)))
def __auto_table_args(cls): return (db.UniqueConstraint('person_id', *cls.person_link_unique_columns), )
class PaperReview(ProposalReviewMixin, RenderModeMixin, db.Model): """Represents a paper review, emitted by a layout or content reviewer""" possible_render_modes = {RenderMode.markdown} default_render_mode = RenderMode.markdown revision_attr = 'revision' group_attr = 'type' group_proxy_cls = PaperTypeProxy __tablename__ = 'reviews' __table_args__ = (db.UniqueConstraint('revision_id', 'user_id', 'type'), { 'schema': 'event_paper_reviewing' }) TIMELINE_TYPE = 'review' id = db.Column(db.Integer, primary_key=True) revision_id = db.Column( db.Integer, db.ForeignKey('event_paper_reviewing.revisions.id'), index=True, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('users.users.id'), index=True, nullable=False) created_dt = db.Column( UTCDateTime, nullable=False, default=now_utc, ) modified_dt = db.Column(UTCDateTime, nullable=True) _comment = db.Column('comment', db.Text, nullable=False, default='') type = db.Column(PyIntEnum(PaperReviewType), nullable=False) proposed_action = db.Column(PyIntEnum(PaperAction), nullable=False) revision = db.relationship('PaperRevision', lazy=True, backref=db.backref('reviews', lazy=True, order_by=created_dt.desc())) user = db.relationship('User', lazy=True, backref=db.backref('paper_reviews', lazy='dynamic')) # relationship backrefs: # - ratings (PaperReviewRating.review) comment = RenderModeMixin.create_hybrid_property('_comment') @locator_property def locator(self): return dict(self.revision.locator, review_id=self.id) @return_ascii def __repr__(self): return format_repr(self, 'id', 'type', 'revision_id', 'user_id', proposed_action=None) def can_edit(self, user, check_state=False): from fossir.modules.events.papers.models.revisions import PaperRevisionState if user is None: return False if check_state and self.revision.state != PaperRevisionState.submitted: return False return self.user == user def can_view(self, user): if user is None: return False elif user == self.user: return True elif self.revision.paper.can_judge(user): return True return False @property def visibility(self): return PaperCommentVisibility.reviewers @property def score(self): ratings = [r for r in self.ratings] if not ratings: return None return sum(x.value for x in ratings) / len(ratings)
def __auto_table_args(): return db.UniqueConstraint('module', 'name'),
class Room(versioned_cache(_cache, 'id'), db.Model, Serializer): __tablename__ = 'rooms' __table_args__ = ( db.UniqueConstraint( 'id', 'location_id'), # useless but needed for the LocationMixin fkey { 'schema': 'roombooking' }) __public__ = [ 'id', 'name', 'location_name', 'floor', 'number', 'building', 'booking_url', 'capacity', 'comments', 'owner_id', 'details_url', 'large_photo_url', 'small_photo_url', 'has_photo', 'is_active', 'is_reservable', 'is_auto_confirm', 'marker_description', 'kind', 'booking_limit_days' ] __public_exhaustive__ = __public__ + [ 'has_webcast_recording', 'has_vc', 'has_projector', 'is_public', 'has_booking_groups' ] __calendar_public__ = [ 'id', 'building', 'name', 'floor', 'number', 'kind', 'booking_url', 'details_url', 'location_name', 'max_advance_days' ] __api_public__ = ('id', 'building', 'name', 'floor', 'longitude', 'latitude', ('number', 'roomNr'), ('location_name', 'location'), ('full_name', 'fullName'), ('booking_url', 'bookingUrl')) __api_minimal_public__ = ('id', ('full_name', 'fullName')) id = db.Column(db.Integer, primary_key=True) location_id = db.Column(db.Integer, db.ForeignKey('roombooking.locations.id'), nullable=False) photo_id = db.Column(db.Integer, db.ForeignKey('roombooking.photos.id')) name = db.Column(db.String, nullable=False) site = db.Column(db.String, default='') division = db.Column(db.String) building = db.Column(db.String, nullable=False) floor = db.Column(db.String, default='', nullable=False) number = db.Column(db.String, default='', nullable=False) notification_before_days = db.Column(db.Integer) notification_before_days_weekly = db.Column(db.Integer) notification_before_days_monthly = db.Column(db.Integer) notification_for_assistance = db.Column(db.Boolean, nullable=False, default=False) reservations_need_confirmation = db.Column(db.Boolean, nullable=False, default=False) notifications_enabled = db.Column(db.Boolean, nullable=False, default=True) telephone = db.Column(db.String) key_location = db.Column(db.String) capacity = db.Column(db.Integer, default=20) surface_area = db.Column(db.Integer) latitude = db.Column(db.String) longitude = db.Column(db.String) comments = db.Column(db.String) owner_id = db.Column(db.Integer, db.ForeignKey('users.users.id'), index=True, nullable=False) is_active = db.Column(db.Boolean, nullable=False, default=True, index=True) is_reservable = db.Column(db.Boolean, nullable=False, default=True) max_advance_days = db.Column(db.Integer) booking_limit_days = db.Column(db.Integer) attributes = db.relationship('RoomAttributeAssociation', backref='room', cascade='all, delete-orphan', lazy='dynamic') blocked_rooms = db.relationship('BlockedRoom', backref='room', cascade='all, delete-orphan', lazy='dynamic') bookable_hours = db.relationship('BookableHours', backref='room', order_by=BookableHours.start_time, cascade='all, delete-orphan', lazy='dynamic') available_equipment = db.relationship('EquipmentType', secondary=RoomEquipmentAssociation, backref='rooms', lazy='dynamic') nonbookable_periods = db.relationship( 'NonBookablePeriod', backref='room', order_by=NonBookablePeriod.end_dt.desc(), cascade='all, delete-orphan', lazy='dynamic') photo = db.relationship('Photo', backref='room', cascade='all, delete-orphan', single_parent=True, lazy=True) reservations = db.relationship('Reservation', backref='room', cascade='all, delete-orphan', lazy='dynamic') #: The owner of the room. If the room has the `manager-group` #: attribute set, any users in that group are also considered #: owners when it comes to management privileges. #: Use :meth:`is_owned_by` for ownership checks that should #: also check against the management group. owner = db.relationship( 'User', # subquery load since a normal joinedload breaks `get_with_data` lazy='subquery', backref=db.backref('owned_rooms', lazy='dynamic')) # relationship backrefs: # - breaks (Break.own_room) # - contributions (Contribution.own_room) # - events (Event.own_room) # - location (Location.rooms) # - session_blocks (SessionBlock.own_room) # - sessions (Session.own_room) @hybrid_property def is_auto_confirm(self): return not self.reservations_need_confirmation @is_auto_confirm.expression def is_auto_confirm(self): return ~self.reservations_need_confirmation @property def booking_url(self): if self.id is None: return None return url_for('rooms.room_book', self) @property def details_url(self): if self.id is None: return None return url_for('rooms.roomBooking-roomDetails', self) @property def large_photo_url(self): if self.id is None: return None return url_for('rooms.photo', self, size='large') @property def small_photo_url(self): if self.id is None: return None return url_for('rooms.photo', self, size='small') @property def map_url(self): if self.location.map_url_template: return self.location.map_url_template.format( building=self.building, floor=self.floor, number=self.number) else: return None @property def has_photo(self): return self.photo_id is not None @property def full_name(self): if self.has_special_name: return u'{} - {}'.format(self.generate_name(), self.name) else: return u'{}'.format(self.generate_name()) @property def has_special_name(self): return self.name and self.name != self.generate_name() @property @cached(_cache) def has_booking_groups(self): return self.has_attribute('allowed-booking-group') @property @cached(_cache) def has_projector(self): return self.has_equipment(u'Computer Projector', u'Video projector 4:3', u'Video projector 16:9') @property @cached(_cache) def has_webcast_recording(self): return self.has_equipment('Webcast/Recording') @property @cached(_cache) def has_vc(self): return self.has_equipment('Video conference') @property @cached(_cache) def is_public(self): return self.is_reservable and not self.has_booking_groups @property def kind(self): if not self.is_reservable or self.has_booking_groups: return 'privateRoom' elif self.reservations_need_confirmation: return 'moderatedRoom' else: return 'basicRoom' @property def location_name(self): return self.location.name @property def marker_description(self): infos = [] infos.append(u'{capacity} {label}'.format( capacity=self.capacity, label=_(u'person') if self.capacity == 1 else _(u'people'))) infos.append(_(u'public') if self.is_public else _(u'private')) infos.append( _(u'auto-confirmation') if self. is_auto_confirm else _(u'needs confirmation')) if self.has_vc: infos.append(_(u'videoconference')) return u', '.join(map(unicode, infos)) @property def manager_emails(self): manager_group = self.get_attribute_value('manager-group') if not manager_group: return set() group = GroupProxy.get_named_default_group(manager_group) return {u.email for u in group.get_members()} @property def notification_emails(self): return set( filter( None, map( unicode.strip, self.get_attribute_value(u'notification-email', u'').split(u',')))) @return_ascii def __repr__(self): return u'<Room({0}, {1}, {2})>'.format(self.id, self.location_id, self.name) @cached(_cache) def has_equipment(self, *names): return self.available_equipment.filter( EquipmentType.name.in_(names)).count() > 0 def find_available_vc_equipment(self): vc_equipment = (self.available_equipment.correlate(Room).with_entities( EquipmentType.id).filter_by(name='Video conference').as_scalar()) return self.available_equipment.filter( EquipmentType.parent_id == vc_equipment) def get_attribute_by_name(self, attribute_name): return (self.attributes.join(RoomAttribute).filter( RoomAttribute.name == attribute_name).first()) def has_attribute(self, attribute_name): return self.get_attribute_by_name(attribute_name) is not None @cached(_cache) def get_attribute_value(self, name, default=None): attr = self.get_attribute_by_name(name) return attr.value if attr else default def set_attribute_value(self, name, value): attr = self.get_attribute_by_name(name) if attr: if value: attr.value = value else: self.attributes.filter(RoomAttributeAssociation.attribute_id == attr.attribute_id) \ .delete(synchronize_session='fetch') elif value: attr = self.location.get_attribute_by_name(name) if not attr: raise ValueError( "Attribute {} not supported in location {}".format( name, self.location_name)) attr_assoc = RoomAttributeAssociation() attr_assoc.value = value attr_assoc.attribute = attr self.attributes.append(attr_assoc) db.session.flush() @locator_property def locator(self): return {'roomLocation': self.location_name, 'roomID': self.id} def generate_name(self): return u'{}-{}-{}'.format(self.building, self.floor, self.number) def update_name(self): if not self.has_special_name and self.building and self.floor and self.number: self.name = self.generate_name() @classmethod def find_all(cls, *args, **kwargs): """Retrieves rooms, sorted by location and full name""" rooms = super(Room, cls).find_all(*args, **kwargs) rooms.sort( key=lambda r: natural_sort_key(r.location_name + r.full_name)) return rooms @classmethod def find_with_attribute(cls, attribute): """Search rooms which have a specific attribute""" return (Room.query.with_entities( Room, RoomAttributeAssociation.value).join( Room.attributes, RoomAttributeAssociation.attribute).filter( RoomAttribute.name == attribute).all()) @staticmethod def get_with_data(*args, **kwargs): from fossir.modules.rb.models.locations import Location only_active = kwargs.pop('only_active', True) filters = kwargs.pop('filters', None) order = kwargs.pop( 'order', [Location.name, Room.building, Room.floor, Room.number, Room.name]) if kwargs: raise ValueError('Unexpected kwargs: {}'.format(kwargs)) query = Room.query entities = [Room] if 'equipment' in args: entities.append(static_array.array_agg(EquipmentType.name)) query = query.outerjoin(RoomEquipmentAssociation).outerjoin( EquipmentType) if 'vc_equipment' in args or 'non_vc_equipment' in args: vc_id_subquery = db.session.query(EquipmentType.id) \ .correlate(Room) \ .filter_by(name='Video conference') \ .join(RoomEquipmentAssociation) \ .filter(RoomEquipmentAssociation.c.room_id == Room.id) \ .as_scalar() if 'vc_equipment' in args: # noinspection PyTypeChecker entities.append( static_array.array( db.session.query(EquipmentType.name).join( RoomEquipmentAssociation).filter( RoomEquipmentAssociation.c.room_id == Room.id, EquipmentType.parent_id == vc_id_subquery).order_by( EquipmentType.name).as_scalar())) if 'non_vc_equipment' in args: # noinspection PyTypeChecker entities.append( static_array.array( db.session.query(EquipmentType.name).join( RoomEquipmentAssociation).filter( RoomEquipmentAssociation.c.room_id == Room.id, (EquipmentType.parent_id == None) | (EquipmentType.parent_id != vc_id_subquery)).order_by( EquipmentType.name).as_scalar())) query = (query.with_entities(*entities).outerjoin( Location, Location.id == Room.location_id).group_by(Location.name, Room.id)) if only_active: query = query.filter(Room.is_active) if filters: # pragma: no cover query = query.filter(*filters) if order: # pragma: no cover query = query.order_by(*order) keys = ('room', ) + tuple(args) return (dict(zip(keys, row if args else [row])) for row in query) @classproperty @staticmethod def max_capacity(): return db.session.query(db.func.max(Room.capacity)).scalar() or 0 @staticmethod def filter_available(start_dt, end_dt, repetition, include_pre_bookings=True, include_pending_blockings=True): """Returns a SQLAlchemy filter criterion ensuring that the room is available during the given time.""" # Check availability against reservation occurrences dummy_occurrences = ReservationOccurrence.create_series( start_dt, end_dt, repetition) overlap_criteria = ReservationOccurrence.filter_overlap( dummy_occurrences) reservation_criteria = [ Reservation.room_id == Room.id, ReservationOccurrence.is_valid, overlap_criteria ] if not include_pre_bookings: reservation_criteria.append(Reservation.is_accepted) occurrences_filter = Reservation.occurrences.any( and_(*reservation_criteria)) # Check availability against blockings if include_pending_blockings: valid_states = (BlockedRoom.State.accepted, BlockedRoom.State.pending) else: valid_states = (BlockedRoom.State.accepted, ) blocking_criteria = [ BlockedRoom.blocking_id == Blocking.id, BlockedRoom.state.in_(valid_states), Blocking.start_date <= start_dt.date(), Blocking.end_date >= end_dt.date() ] blockings_filter = Room.blocked_rooms.any(and_(*blocking_criteria)) return ~occurrences_filter & ~blockings_filter @staticmethod def find_with_filters(filters, user=None): from fossir.modules.rb.models.locations import Location equipment_count = len(filters.get('available_equipment', ())) equipment_subquery = None if equipment_count: equipment_subquery = ( db.session.query(RoomEquipmentAssociation).with_entities( func.count(RoomEquipmentAssociation.c.room_id)).filter( RoomEquipmentAssociation.c.room_id == Room.id, RoomEquipmentAssociation.c.equipment_id.in_( eq.id for eq in filters['available_equipment']) ).correlate(Room).as_scalar()) capacity = filters.get('capacity') q = (Room.query.join(Location.rooms).filter( Location.id == filters['location'].id if filters.get('location') else True, ((Room.capacity >= (capacity * 0.8)) | (Room.capacity == None)) if capacity else True, Room.is_reservable if filters.get('is_only_public') else True, Room.is_auto_confirm if filters.get('is_auto_confirm') else True, Room.is_active if filters.get('is_only_active', False) else True, (equipment_subquery == equipment_count) if equipment_subquery is not None else True)) if filters.get('available', -1) != -1: repetition = RepeatMapping.convert_legacy_repeatability( ast.literal_eval(filters['repeatability'])) is_available = Room.filter_available( filters['start_dt'], filters['end_dt'], repetition, include_pre_bookings=filters.get('include_pre_bookings', True), include_pending_blockings=filters.get( 'include_pending_blockings', True)) # Filter the search results if filters['available'] == 0: # booked/unavailable q = q.filter(~is_available) elif filters['available'] == 1: # available q = q.filter(is_available) else: raise ValueError('Unexpected availability value') free_search_columns = ('name', 'site', 'division', 'building', 'floor', 'number', 'telephone', 'key_location', 'comments') if filters.get('details'): # Attributes are stored JSON-encoded, so we need to JSON-encode the provided string and remove the quotes # afterwards since PostgreSQL currently does not expose a function to decode a JSON string: # http://www.postgresql.org/message-id/[email protected] details = filters['details'].lower() details_str = u'%{}%'.format(escape_like(details)) details_json = u'%{}%'.format( escape_like(json.dumps(details)[1:-1])) free_search_criteria = [ getattr(Room, c).ilike(details_str) for c in free_search_columns ] free_search_criteria.append( Room.attributes.any( cast(RoomAttributeAssociation.value, db.String).ilike(details_json))) q = q.filter(or_(*free_search_criteria)) q = q.order_by(Room.capacity) rooms = q.all() # Apply a bunch of filters which are *much* easier to do here than in SQL! if filters.get('is_only_public'): # This may trigger additional SQL queries but is_public is cached and doing this check here is *much* easier rooms = [r for r in rooms if r.is_public] if filters.get('is_only_my_rooms'): assert user is not None rooms = [r for r in rooms if r.is_owned_by(user)] if capacity: # Unless it would result in an empty resultset we don't want to show rooms with >20% more capacity # than requested. This cannot be done easily in SQL so we do that logic here after the SQL query already # weeded out rooms that are too small matching_capacity_rooms = [ r for r in rooms if r.capacity is None or r.capacity <= capacity * 1.2 ] if matching_capacity_rooms: rooms = matching_capacity_rooms return rooms def has_live_reservations(self): return self.reservations.filter_by(is_archived=False, is_cancelled=False, is_rejected=False).count() > 0 def get_blocked_rooms(self, *dates, **kwargs): states = kwargs.get('states', (BlockedRoom.State.accepted, )) return (self.blocked_rooms.join(BlockedRoom.blocking).options( contains_eager(BlockedRoom.blocking)).filter( or_(Blocking.is_active_at(d) for d in dates), BlockedRoom.state.in_(states)).all()) @unify_user_args def _can_be_booked(self, user, prebook=False, ignore_admin=False): if not user or not rb_check_user_access(user): return False if (not ignore_admin and rb_is_admin(user)) or (self.is_owned_by(user) and self.is_active): return True if self.is_active and self.is_reservable and ( prebook or not self.reservations_need_confirmation): group_name = self.get_attribute_value('allowed-booking-group') if not group_name or user in GroupProxy.get_named_default_group( group_name): return True return False def can_be_booked(self, user, ignore_admin=False): """ Reservable rooms which does not require pre-booking can be booked by anyone. Other rooms - only by their responsibles. """ return self._can_be_booked(user, ignore_admin=ignore_admin) def can_be_prebooked(self, user, ignore_admin=False): """ Reservable rooms can be pre-booked by anyone. Other rooms - only by their responsibles. """ return self._can_be_booked(user, prebook=True, ignore_admin=ignore_admin) def can_be_overridden(self, user): if not user: return False return rb_is_admin(user) or self.is_owned_by(user) def can_be_modified(self, user): """Only admin can modify rooms.""" if not user: return False return rb_is_admin(user) def can_be_deleted(self, user): return self.can_be_modified(user) @unify_user_args @cached(_cache) def is_owned_by(self, user): """Checks if the user is managing the room (owner or manager)""" if self.owner == user: return True manager_group = self.get_attribute_value('manager-group') if not manager_group: return False return user in GroupProxy.get_named_default_group(manager_group) @classmethod def get_owned_by(cls, user): return [ room for room in cls.find(is_active=True) if room.is_owned_by(user) ] @classmethod def user_owns_rooms(cls, user): return any(room for room in cls.find(is_active=True) if room.is_owned_by(user)) def check_advance_days(self, end_date, user=None, quiet=False): if not self.max_advance_days: return True if user and (rb_is_admin(user) or self.is_owned_by(user)): return True advance_days = (end_date - date.today()).days ok = advance_days < self.max_advance_days if quiet or ok: return ok else: msg = _(u'You cannot book this room more than {} days in advance') raise NoReportError(msg.format(self.max_advance_days)) def check_bookable_hours(self, start_time, end_time, user=None, quiet=False): if user and (rb_is_admin(user) or self.is_owned_by(user)): return True bookable_hours = self.bookable_hours.all() if not bookable_hours: return True for bt in bookable_hours: if bt.fits_period(start_time, end_time): return True if quiet: return False raise NoReportError(u'Room cannot be booked at this time')
class AbstractReview(ProposalReviewMixin, RenderModeMixin, db.Model): """Represents an abstract review, emitted by a reviewer""" possible_render_modes = {RenderMode.markdown} default_render_mode = RenderMode.markdown revision_attr = 'abstract' group_attr = 'track' marshmallow_aliases = {'_comment': 'comment'} __tablename__ = 'abstract_reviews' __table_args__ = ( db.UniqueConstraint('abstract_id', 'user_id', 'track_id'), db.CheckConstraint( "proposed_action = {} OR (proposed_contribution_type_id IS NULL)". format(AbstractAction.accept), name='prop_contrib_id_only_accepted'), db.CheckConstraint( "(proposed_action IN ({}, {})) = (proposed_related_abstract_id IS NOT NULL)" .format(AbstractAction.mark_as_duplicate, AbstractAction.merge), name='prop_abstract_id_only_duplicate_merge'), { 'schema': 'event_abstracts' }) id = db.Column(db.Integer, primary_key=True) abstract_id = db.Column(db.Integer, db.ForeignKey('event_abstracts.abstracts.id'), index=True, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('users.users.id'), index=True, nullable=False) track_id = db.Column(db.Integer, db.ForeignKey('events.tracks.id'), index=True, nullable=True) created_dt = db.Column( UTCDateTime, nullable=False, default=now_utc, ) modified_dt = db.Column(UTCDateTime, nullable=True) _comment = db.Column('comment', db.Text, nullable=False, default='') proposed_action = db.Column(PyIntEnum(AbstractAction), nullable=False) proposed_related_abstract_id = db.Column( db.Integer, db.ForeignKey('event_abstracts.abstracts.id'), index=True, nullable=True) proposed_contribution_type_id = db.Column( db.Integer, db.ForeignKey('events.contribution_types.id'), nullable=True, index=True) abstract = db.relationship('Abstract', lazy=True, foreign_keys=abstract_id, backref=db.backref('reviews', cascade='all, delete-orphan', lazy=True)) user = db.relationship('User', lazy=True, backref=db.backref('abstract_reviews', lazy='dynamic')) track = db.relationship('Track', lazy=True, foreign_keys=track_id, backref=db.backref('abstract_reviews', lazy='dynamic')) proposed_related_abstract = db.relationship( 'Abstract', lazy=True, foreign_keys=proposed_related_abstract_id, backref=db.backref('proposed_related_abstract_reviews', lazy='dynamic')) proposed_tracks = db.relationship( 'Track', secondary='event_abstracts.proposed_for_tracks', lazy=True, collection_class=set, backref=db.backref('proposed_abstract_reviews', lazy='dynamic', passive_deletes=True)) proposed_contribution_type = db.relationship('ContributionType', lazy=True, backref=db.backref( 'abstract_reviews', lazy='dynamic')) # relationship backrefs: # - ratings (AbstractReviewRating.review) comment = RenderModeMixin.create_hybrid_property('_comment') @locator_property def locator(self): return dict(self.abstract.locator, review_id=self.id) @return_ascii def __repr__(self): return format_repr(self, 'id', 'abstract_id', 'user_id', proposed_action=None) @property def visibility(self): return AbstractCommentVisibility.reviewers @property def score(self): ratings = [ r for r in self.ratings if not r.question.no_score and not r.question.is_deleted ] if not ratings: return None return sum(x.value for x in ratings) / len(ratings) def can_edit(self, user, check_state=False): if user is None: return False if check_state and self.abstract.public_state.name != 'under_review': return False return self.user == user def can_view(self, user): if user is None: return False elif user == self.user: return True if self.abstract.can_judge(user): return True else: return self.track.can_convene(user)