Exemplo n.º 1
0
class Property(BaseMixin, db.Model):
    __tablename__ = 'property'
    node_id = db.Column(None, db.ForeignKey('node.id'), nullable=False)
    name = db.Column(db.Unicode(40), nullable=False)
    value = db.Column(db.Unicode(250), nullable=False)

    __table_args__ = (db.UniqueConstraint('name', 'node_id'), )
Exemplo n.º 2
0
class Website(BaseNameMixin, db.Model):
    __tablename__ = 'website'
    #: URL to the website
    url = db.Column(db.Unicode(80), nullable=False, default=u'')
    #: Theme that this website uses as the default (folders can override)
    theme = db.Column(db.Unicode(80), nullable=False, default=u'default')
    #: Typekit code, if used
    typekit_code = db.Column(db.Unicode(20), nullable=False, default=u'')
    #: Google Analytics code, if used
    googleanalytics_code = db.Column(db.Unicode(20), nullable=False, default=u'')

    _hostnames = db.relationship("Hostname", cascade='all, delete-orphan', backref='website')
    hostnames = association_proxy('_hostnames', 'name',
        creator=lambda name: Hostname.get(name=name))

    def __init__(self, **kwargs):
        super(Website, self).__init__(**kwargs)
        root = Folder(name=u'', title=u'', website=self)
        self.folders.append(root)
        #root.pages[0].template = u'index.html'

    def __repr__(self):
        return u'<Website %s "%s">' % (self.name, self.title)

    def folder_ids(self):
        return [i[0] for i in db.session.query(Folder.id).filter_by(website=self).all()]

    def url_for(self, action='view'):
        if action == 'view':  # View in event app
            return url_for('index')
        elif action == 'list':  # Folder listing in admin app
            return url_for('website', website=self.name)
        elif action == 'edit':  # Edit website settings
            return url_for('website_edit', website=self.name)
Exemplo n.º 3
0
class File(BaseScopedNameMixin, db.Model):
    __tablename__ = 'file'
    file_folder_id = db.Column(None,
                               db.ForeignKey('file_folder.id'),
                               nullable=False)
    file_folder = db.relationship(FileFolder)
    parent = db.synonym('file_folder')
    url = db.Column(db.Unicode(250), nullable=False)

    __table_args__ = (db.UniqueConstraint('file_folder_id', 'name'))
Exemplo n.º 4
0
class Folder(BaseScopedNameMixin, db.Model):
    __tablename__ = 'folder'
    #: Website this folder is under
    website_id = db.Column(db.Integer,
                           db.ForeignKey('website.id'),
                           nullable=False)

    _theme = db.Column("theme", db.Unicode(80), nullable=False, default=u'')

    website = db.relationship(Website,
                              backref=db.backref('folders',
                                                 order_by='Folder.name',
                                                 cascade='all, delete-orphan'))
    parent = db.synonym('website')
    __table_args__ = (db.UniqueConstraint('name', 'website_id'), )

    @property
    def theme(self):
        return self._theme or self.website.theme

    @theme.setter
    def theme(self, value):
        self._theme = value

    #: Theme used by the folder. Defaults to the website's theme.
    theme = db.synonym('_theme', descriptor=theme)

    def __init__(self, **kwargs):
        super(Folder, self).__init__(**kwargs)
        #index = Page(name=u'', title=u'Index', folder=self, template=u'page.html')
        #index.name = u''  # Reset it to a blank
        #self.pages.append(index)

    def __repr__(self):
        return u'<Folder %s at %s>' % (self.name
                                       or '(root)', self.website.name)

    def url_for(self, action='view'):
        """
        Returns a view URL based on the website's URL field.
        """
        if action == 'view':
            return urljoin(self.website.url, self.name)
        elif action == 'list':
            if self.name == u'':
                return url_for('website', website=self.website.name)
            else:
                return url_for('folder',
                               website=self.website.name,
                               folder=self.name)
        elif action == 'edit':
            if self.name != u'':
                return url_for('folder_edit',
                               website=self.website.name,
                               folder=self.name)
Exemplo n.º 5
0
class EventAttendee(BaseMixin, db.Model):
    __tablename__ = 'event_attendee'
    # User who is attending
    user_id = db.Column(None, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship(User)
    # Event that the user is attending
    event_id = db.Column(None, db.ForeignKey('event.id'), nullable=False)
    event = db.relationship(Event,
                            backref=db.backref('attendees',
                                               cascade='all, delete-orphan'))
    # Status codes: U/known, Y/es, N/o, M/aybe, W/ait-listed
    status = db.Column(db.Unicode(1), nullable=False, default=u'U')
    __table_args__ = (db.UniqueConstraint('user_id', 'event_id'), )
Exemplo n.º 6
0
class Hostname(BaseMixin, db.Model):
    __tablename__ = 'hostname'
    #: Hostname that a website may be accessed at. Typically name:port
    name = db.Column(db.String(80), unique=True, nullable=False)
    #: Website this hostname applies to
    website_id = db.Column(db.Integer, db.ForeignKey('website.id'), nullable=False)

    def __repr__(self):
        return u'<Hostname %s>' % self.name

    @classmethod
    def get(cls, name, website=None):
        hostname = cls.query.filter_by(name=name).first()
        return hostname or cls(name=name, website=website)
Exemplo n.º 7
0
class LoginCode(BaseMixin, db.Model):
    __tablename__ = 'logincode'
    #: Tracking code to enable users to login to an event website
    code = db.Column(db.Unicode(22), nullable=False, unique=True)
    #: Access scope requested
    scope = db.Column(db.Unicode(250), nullable=False, default=u'')
    #: User who logged in
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True, default=None)
    user = db.relationship(User)
    #: URL on event website to return user to
    next_url = db.Column(db.Unicode(250), nullable=False)
    #: Login handler URL on event website
    return_url = db.Column(db.Unicode(250), nullable=False)

    def __init__(self, **kwargs):
        super(LoginCode, self).__init__(**kwargs)
        self.code = newid()
Exemplo n.º 8
0
 def id(cls):
     """Link back to node"""
     return db.Column(db.Integer,
                      db.ForeignKey('node.id'),
                      primary_key=True,
                      nullable=False)
Exemplo n.º 9
0
class Node(BaseScopedNameMixin, db.Model):
    __tablename__ = 'node'
    #: Id of the node across sites (staging, production, etc) for import/export
    uuid = db.Column(db.Unicode(22),
                     unique=True,
                     default=newid,
                     nullable=False)
    #: User who made this node
    user_id = db.Column(db.Integer,
                        db.ForeignKey('user.id'),
                        nullable=False,
                        default=default_user_id)
    user = db.relationship(User)
    #: Folder in which this node is located
    folder_id = db.Column(db.Integer,
                          db.ForeignKey('folder.id'),
                          nullable=False)
    folder = db.relationship(Folder,
                             backref=db.backref('nodes',
                                                order_by='Node.name',
                                                cascade='all, delete-orphan'))
    parent = db.synonym('folder')
    #: Publication date
    published_at = db.Column(db.DateTime,
                             default=datetime.utcnow,
                             nullable=False)
    #: Type of node, for polymorphic identity
    type = db.Column('type', db.Unicode(20))
    __table_args__ = (db.UniqueConstraint('name', 'folder_id'), )
    __mapper_args__ = {'polymorphic_on': type}

    node_properties = db.relationship(
        Property,
        cascade='all, delete-orphan',
        collection_class=attribute_mapped_collection('name'),
        backref='node')

    @property
    def properties(self):
        if not hasattr(self, '_cached_properties'):
            self._cached_properties = _NodeProperties(self.node_properties,
                                                      node=self)
        return self._cached_properties

    @properties.setter
    def properties(self, value):
        if not isinstance(value, dict):
            raise ValueError("Value is not a dictionary")
        for key in list(self.node_properties.keys()):
            if key not in value:
                self.node_properties.pop(key)
        self._cached_properties = _NodeProperties(value, node=self)

    def as_json(self):
        return {
            'uuid': self.uuid,
            'name': self.name,
            'title': self.title,
            'created_at': self.created_at.isoformat() + 'Z',
            'updated_at': self.updated_at.isoformat() + 'Z',
            'published_at': self.published_at.isoformat() + 'Z',
            'userid': self.user.userid,
            'type': self.type,
            'properties': self.properties,
        }

    def import_from(self, data):
        self.uuid = data['uuid']
        self.name = data['name']
        self.title = data['title']
        self.published_at = parse_isoformat(data['published_at'])
        self.properties = data['properties']

    def import_from_internal(self, data):
        # Only required for nodes that keep internal references to other nodes
        pass

    def url_for(self, action='view'):
        """
        Return a URL to this node.
        """
        if action == 'view':
            if self.folder.name == u'':
                return url_for('folder', folder=self.name)
            else:
                return url_for('node', folder=self.folder.name, node=self.name)
        elif action == 'edit':
            return url_for('node_edit',
                           website=self.folder.website.name,
                           folder=self.folder.name,
                           node=self.name)
        elif action == 'delete':
            return url_for('node_delete',
                           website=self.folder.website.name,
                           folder=self.folder.name,
                           node=self.name)
Exemplo n.º 10
0
class Event(ContentMixin, Node):
    __tablename__ = 'event'

    #: Start datetime for the event (in UTC)
    start_datetime = db.Column(db.DateTime, nullable=False)
    #: End datetime for the event (in UTC)
    end_datetime = db.Column(db.DateTime, nullable=False)
    #: Timezone as a string
    timezone = db.Column(db.Unicode(32), nullable=False)
    #: Location name
    location_name = db.Column(db.Unicode(80), nullable=False, default=u'')
    #: Location address
    location_address = db.Column(db.Unicode(250), nullable=False, default=u'')
    #: Location on map
    map_id = db.Column(None, db.ForeignKey('map.id'), nullable=True)
    map = db.relationship(Map, primaryjoin=map_id == Map.id)
    #: Map marker
    mapmarker = db.Column(db.Unicode(80), nullable=False, default=u'')
    #: Venue capacity, if attendance is capped
    capacity = db.Column(db.Integer, nullable=False, default=0)
    #: Allow wait-listing?
    allow_waitlisting = db.Column(db.Boolean, nullable=False, default=False)
    #: Allow a Maybe response?
    allow_maybe = db.Column(db.Boolean, nullable=False, default=True)
    #: Participant list to limit attendees to
    participant_list_id = db.Column(None,
                                    db.ForeignKey('participant_list.id'),
                                    nullable=True)
    participant_list = db.relationship(
        ParticipantList, primaryjoin=participant_list_id == ParticipantList.id)

    def _localize_time(self, value):
        return utc.localize(value).astimezone(timezone(self.timezone))

    @property
    def starts_at(self):
        return self._localize_time(self.start_datetime)

    @property
    def ends_at(self):
        return self._localize_time(self.end_datetime)

    def count(self):
        """Return count of confirmed attendees."""
        return len([a for a in self.attendees if a.status == u'Y'])

    def has_capacity(self):
        """Does the event have spare capacity for more attendees?"""
        if self.capacity == 0:
            return True
        else:
            return self.count() < self.capacity

    def can_rsvp(self, user):
        """Is this user authorized to participate?"""
        if self.participant_list:
            return self.participant_list.has_user(user)
        else:
            return True

    def set_status(self, user, status):
        """Set RSVP status for user."""
        if status not in [u'Y', u'N', u'M']:
            raise ValueError("Invalid status")
        if status == u'M' and not self.allow_maybe:
            raise ValueError(u"A “Maybe” response is not allowed")
        if not self.can_rsvp(user):
            raise ValueError("This user cannot participate")
        attendee = EventAttendee.query.filter_by(event=self, user=user).first()
        if not attendee:
            attendee = EventAttendee(event=self, user=user)
        if status == u'Y' and not self.has_capacity():
            if self.allow_waitlisting:
                status = u'W'
            else:
                raise ValueError("This event is over capacity")
        db.session.add(attendee)
        attendee.status = status

    def get_status(self, user):
        """Get RSVP status for this user."""
        attendee = EventAttendee.query.filter_by(event=self, user=user).first()
        if not attendee:
            return u'U'
        else:
            return attendee.status

    def url_for(self, action='view'):
        if action == 'rsvp':
            base = super(Event, self).url_for('view')
            if base.endswith('/'):
                return base + 'rsvp'
            else:
                return base + '/rsvp'
        elif action in ['list', 'csv', 'update']:
            return url_for('node_action',
                           website=self.folder.website.name,
                           folder=self.folder.name,
                           node=self.name,
                           action=action)
        else:
            return super(Event, self).url_for(action)

    def as_json(self):
        result = super(ContentMixin, self).as_json()
        result['start_datetime'] = self.start_datetime.isoformat() + 'Z'
        result['end_datetime'] = self.end_datetime.isoformat() + 'Z'
        result['timezone'] = self.timezone
        result['location_name'] = self.location_name
        result['location_address'] = self.location_address
        result['map'] = self.map.uuid if self.map else None
        result['mapmarker'] = self.mapmarker
        result['capacity'] = self.capacity
        result['allow_waitlisting'] = self.allow_waitlisting
        result['allow_maybe'] = self.allow_maybe
        result[
            'participant_list'] = self.participant_list.uuid if self.participant_list else None
        return result

    def import_from(self, data):
        super(ContentMixin, self).import_from(data)
        self.start_datetime = parse_isoformat(data['start_datetime'])
        self.end_datetime = parse_isoformat(data['end_datetime'])
        self.timezone = data['timezone']
        self.location_name = data['location_name']
        self.location_address = data['location_address']
        self.mapmarker = data['mapmarker']
        self.capacity = data['capacity']
        self.allow_waitlisting = data['allow_waitlisting']
        self.allow_maybe = data['allow_maybe']

    def import_from_internal(self, data):
        super(ContentMixin, self).import_from_internal(data)
        if data.get('map'):
            self.map = Map.query.filter_by(uuid=data['map']).first()
        if data.get('participant_list'):
            self.participant_list = ParticipantList.query.filter_by(
                uuid=data['participant_list']).first()