Example #1
0
class Presentation(db.Model):
    """Presentation of a talk."""

    __tablename__ = 'presentations'

    id = db.Column(db.Integer, primary_key=True)

    slot_id = db.Column(db.Integer, db.ForeignKey('slots.id'), nullable=False)
    slot = db.relationship('Slot',
                           backref=db.backref('presentation', uselist=False))

    talk_id = db.Column(db.Integer, db.ForeignKey('talks.id'), nullable=False)
    talk = db.relationship('Talk',
                           backref=db.backref('presentation', uselist=False))

    def __str__(self):
        """Return a printable representation."""
        return str(self.talk)

    def is_in_all_rooms(self):
        """Return whether the instance is in all rooms."""
        return self.slot.number_of_rooms == 4

    @cached_property
    def number_of_rooms(self):
        """Return the number of rooms for the instance."""
        return len(self.slot.rooms)
Example #2
0
class Room(db.Model):
    """Room of talks."""

    __tablename__ = 'rooms'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    order = db.Column(db.Integer, nullable=False)

    def __str__(self):
        """Return a printable representation."""
        return self.name
Example #3
0
class Category(db.Model):
    """Talk category."""

    __tablename__ = 'categories'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(75), unique=True, nullable=False)

    def __str__(self):
        """Return a printable representation."""
        return self.name
Example #4
0
class Volunteer(db.Model):
    """Volunteer."""

    __tablename__ = 'volunteers'
    query_class = EventQuery

    id = db.Column(db.Integer, primary_key=True)

    event_id = db.Column(db.ForeignKey('events.id'))
    event = db.relationship('Event', backref='volunteers')

    user_id = db.Column(db.ForeignKey('users.id'))
    user = db.relationship('User', uselist=False)

    def __str__(self):
        """Return a printable representation."""
        return self.user.name
Example #5
0
class Duration(db.Model):
    """Talk duration."""

    __tablename__ = 'durations'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    duration = db.Column(db.Integer, nullable=False)
    inactive = db.Column(db.Boolean, default=False)

    __mapper_args__ = {
        'order_by': (inactive, duration),
    }

    def __str__(self):
        """Return a printable representation."""
        return self.name
Example #6
0
class Level(db.Model):
    """Sponsorship level."""

    __tablename__ = 'sponsor_levels'
    query_class = EventQuery

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text)
    order = db.Column(db.Integer, default=0)
    cost = db.Column(db.String, default=0)  # This isn't always money.
    limit = db.Column(db.Integer, default=0)

    event_id = db.Column(
        db.Integer, db.ForeignKey('events.id'), nullable=False)
    event = db.relationship(
        'Event', backref=db.backref('sponsor_levels', lazy='dynamic'))

    def __str__(self):
        """Return a printable representation."""
        return self.name

    @cached_property
    def accepted_sponsors(self):
        """Return the accepted sponsors for the level."""
        return self.sponsors.filter(Sponsor.accepted == True)  # NOQA

    @cached_property
    def is_sold_out(self):
        """Return whether the level is sold out."""
        return 0 < self.limit <= self.accepted_sponsors.count()
Example #7
0
class Announcement(db.Model):
    """News announcement."""

    __tablename__ = 'announcements'
    query_class = EventQuery

    id = db.Column(db.Integer, primary_key=True)
    slug = db.Column(db.String(255), nullable=False)
    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)

    active = db.Column(db.Boolean, nullable=False)
    published = db.Column(ArrowType)

    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.id'),
                         nullable=False)
    event = db.relationship('Event',
                            backref=db.backref('announcements',
                                               lazy='dynamic'))

    def __str__(self):
        """Return a printable representation."""
        return self.title

    @observes('title')
    def _create_slug(self, title):
        """Create a slug from the title of the announcement."""
        self.slug = slugify(self.title)
Example #8
0
class AboutPage(db.Model):
    """About page."""

    __tablename__ = 'about_pages'
    query_class = EventQuery

    id = db.Column(db.Integer, primary_key=True)
    # TODO: validate that the navbar_section / slug combination do not conflict
    # with an existing generated blueprint view route

    # The navbar_path dictates the location of this menu item in the
    # navbar hierarchy.
    navbar_path = db.Column(postgresql.ARRAY(db.String), nullable=False)
    # A slug may be empty. If it is, the item will be placed at the
    # root of the navbar hierarchy.
    slug = db.Column(db.String(255), default='', nullable=False)
    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    active = db.Column(db.Boolean, nullable=False)

    event_id = db.Column(
        db.Integer, db.ForeignKey('events.id'), nullable=False,
    )
    event = db.relationship(
        'Event', backref=db.backref('about_pages', lazy='dynamic'),
    )

    __table_args__ = (
        db.UniqueConstraint(
            'navbar_path', 'slug', 'event_id',
            name='ix_about_pages_navbar_path_slug_event_id',
        ),
    )

    def __str__(self):
        """Return a printable representation."""
        return self.title

    @observes('title')
    def _create_slug(self, title):
        """Create the slug for the page."""
        if not self.slug:
            self.slug = slugify(self.title)

    @property
    def rst_document(self):
        """Return the full reST document, including the title.

        The page's title was be used as the document heading, causing
        any headings defined in the page's content to be used as
        subheadings. To cut down on potential collisions, ``#`` symbols
        will be placed on the lines before and after the title.
        """
        lines = ('{divider}', '{page.title}', '{divider}', '{page.content}')
        return '\n'.join(lines).format(
            divider='#' * len(self.title), page=self)
Example #9
0
class Role(db.Model, RoleMixin):
    """User role."""

    __tablename__ = 'roles'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True)
    description = db.Column(db.String(255))

    def __str__(self):
        """Return a printable representation."""
        return self.name

    def __eq__(self, other):
        return self.name == other or self.name == getattr(other, 'name', None)

    def __hash__(self):
        return id(self)

    def __ne__(self, other):
        return self.name != other and self.name != getattr(other, 'name', None)
Example #10
0
class CallToAction(db.Model):
    """Call to action."""

    __tablename__ = 'calls_to_action'
    query_class = EventQuery

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255), nullable=False)
    url = db.Column(URLType)

    active = db.Column(db.Boolean, nullable=False)
    begins = db.Column(ArrowType)
    ends = db.Column(ArrowType)

    event_id = db.Column(
        db.Integer, db.ForeignKey('events.id'), nullable=False)
    event = db.relationship(
        'Event', backref=db.backref('calls_to_action', lazy='dynamic'))

    def __str__(self):
        """Return a printable representation."""
        return self.title
Example #11
0
class Slot(db.Model):
    """Time slot."""

    __tablename__ = 'slots'

    id = db.Column(db.Integer, primary_key=True)
    kind = db.Column(
        db.Enum('break',
                'meal',
                'keynote',
                'talk',
                'tutorial',
                name='slotkind'),
        nullable=False,
    )
    content_override = db.Column(db.Text)
    start = db.Column(db.Time, nullable=False)
    end = db.Column(db.Time, nullable=False)

    day_id = db.Column(db.Integer, db.ForeignKey('days.id'), nullable=False)
    day = db.relationship('Day', backref=db.backref('slots', lazy='dynamic'))

    rooms = db.relationship(
        'Room',
        secondary=rooms_slots,
        backref=db.backref('slots', lazy='dynamic'),
        order_by=Room.order,
    )

    def __str__(self):
        """Return a printable representation."""
        start = self.start.strftime('%I:%M %p')
        end = self.end.strftime('%I:%M %p')
        rooms = ', '.join(map(str, self.rooms))
        return '{} - {} on {}, {}'.format(start, end, self.day, rooms)

    @cached_property
    def duration(self):
        """Return the duration as a :class:`~datetime.timedelta`."""
        return self.end - self.start
Example #12
0
class Sponsor(db.Model):
    """Sponsor."""

    __tablename__ = 'sponsors'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text)
    url = db.Column(db.String(255))
    logo = db.Column(db.String(255))
    contact_name = db.Column(db.String(255))
    contact_email = db.Column(db.String(255))
    accepted = db.Column(db.Boolean)
    payment_received = db.Column(db.Boolean)
    twitter_handle = db.Column(db.String(15))
    level_id = db.Column(
        db.Integer, db.ForeignKey('sponsor_levels.id'), nullable=False,
    )
    level = db.relationship(
        'Level', backref=db.backref('sponsors', lazy='dynamic'),
    )

    applicant_id = db.Column(
        db.Integer, db.ForeignKey('users.id'), nullable=False,
    )
    applicant = db.relationship('User')

    def __str__(self):
        """Return a printable representation."""
        return self.name

    @cached_property
    def slug(self):
        """Return the slug for the sponsor."""
        return slugify(self.name)
Example #13
0

def pairwise(iterable):
    """Return values from ``iterable`` two at a time.

    Recipe from
    https://docs.python.org/3/library/itertools.html#itertools-recipes.
    """
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)


rooms_slots = db.Table(
    'rooms_slots',
    db.Column('slot_id', db.Integer, db.ForeignKey('slots.id')),
    db.Column('room_id', db.Integer, db.ForeignKey('rooms.id')),
)


class Day(db.Model):
    """Day of talks."""

    __tablename__ = 'days'

    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.Date)
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.id'),
                         nullable=False)
    event = db.relationship('Event',
Example #14
0
class Event(db.Model):
    """Event."""

    __tablename__ = 'events'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), unique=True, nullable=False)
    slug = db.Column(db.String(75), unique=True, nullable=True)

    # Event dates
    begins = db.Column(db.Date)
    ends = db.Column(db.Date)

    # Fields to control when the event is active
    active = db.Column(db.Boolean, nullable=False)
    activity_begins = db.Column(ArrowType)
    activity_ends = db.Column(ArrowType)

    # Proposal window
    proposals_begin = db.Column(ArrowType)
    proposals_end = db.Column(ArrowType)

    # When to publish the talks
    talk_list_begins = db.Column(ArrowType)
    # When to publish the schedule
    talk_schedule_begins = db.Column(ArrowType)

    # Registration information
    registration_closed = db.Column(
        db.Boolean,
        server_default='false',
        nullable=False,
    )
    registration_url = db.Column(db.String(255))
    registration_begins = db.Column(ArrowType)
    registration_ends = db.Column(ArrowType)

    def __str__(self):
        """Return a printable representation."""
        return self.name

    @cached_property
    def accepted_talks(self):
        """Return the accepted :class:`~pygotham.models.Talk` list."""
        return self.talks.filter(Talk.status == 'accepted').order_by(Talk.name)

    @observes('name')
    def _create_slug(self, title):
        """Create a slug from the name of the event."""
        if not self.slug:
            self.slug = slugify(self.name)

    @property
    def dates(self):
        """Return the date(s) for the event."""
        dates = ['{:%B %d}'.format(self.begins)]
        if self.begins != self.ends:
            if self.begins.month == self.ends.month:
                format = '{:%d}'
            else:
                format = '{:%B %d}'
            dates.append(format.format(self.ends))
        return ' - '.join(dates)

    @property
    def is_call_for_proposals_active(self):
        """Return whether the call for proposals for an event is active.

        The CFP is active when the current :class:`~datetime.datetime`
        is greater than or equal to
        :attribute:`~pygotham.events.models.Event.proposals_begin` and
        less than
        :attribute:`~pygotham.events.models.Event.proposals_end`.
        """
        now = arrow.utcnow().to(current_app.config['TIME_ZONE']).naive
        if not self.proposals_begin or now < self.proposals_begin.naive:
            return False
        if self.proposals_end and self.proposals_end.naive < now:
            return False

        return True

    @property
    def is_call_for_proposals_expired(self):
        """Return whether the call for proposals has expired."""
        now = arrow.utcnow().to(current_app.config['TIME_ZONE']).naive
        return self.proposals_end and self.proposals_end.naive < now

    @property
    def is_registration_active(self):
        """Return whether registration for an event is active.

        There are several pieces to the logic of whether or not an
        event's registration is active:

        - :attribute:`~pygotham.events.models.Event.registration_closed`
          must be ``False``.
        - :attribute:`~pygotham.events.models.Event.registration_url`
          must be set.
        - :attribute:`~pygotham.events.models.Event.registration_begins`
          must be earlier than the current date and time.
        - :attribute:`~pygotham.events.models.Event.registration_ends`
          must be ``None`` or later than the current date and time.

        """
        if self.registration_closed:
            return False

        if not self.registration_url:
            return False

        now = arrow.utcnow().to(current_app.config['TIME_ZONE']).naive
        begins = self.registration_begins
        if not begins or now < begins.naive:
            return False
        ends = self.registration_ends
        if ends and ends.naive < now:
            return False

        return True

    @property
    def schedule_is_published(self):
        """Return whether the schedule for an event is published."""
        now = arrow.utcnow().to(current_app.config['TIME_ZONE']).naive
        talk_schedule_begins = self.talk_schedule_begins
        if not talk_schedule_begins or talk_schedule_begins.naive > now:
            return False
        return True

    @property
    def talks_are_published(self):
        """Return whether the talk list for an event is published."""
        now = arrow.utcnow().to(current_app.config['TIME_ZONE']).naive
        if not self.talk_list_begins or self.talk_list_begins.naive > now:
            return False
        return True
Example #15
0
class User(db.Model, UserMixin):
    """User."""

    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), info={'label': 'Name'})
    email = db.Column(db.String(255), unique=True, nullable=False)
    password = db.Column(db.String(130))
    active = db.Column(db.Boolean)
    confirmed_at = db.Column(db.DateTime)
    last_login_at = db.Column(db.DateTime)
    current_login_at = db.Column(db.DateTime)
    last_login_ip = db.Column(db.String(100))
    current_login_ip = db.Column(db.String(100))
    login_count = db.Column(db.Integer)
    registered_at = db.Column(db.DateTime)

    bio = db.Column(db.Text)

    twitter_handle = db.Column(db.String(15))

    roles = db.relationship(
        'Role',
        secondary=roles_users,
        backref=db.backref('users', lazy='dynamic'),
    )

    def __str__(self):
        """Return a printable representation."""
        return self.name or self.email

    def __eq__(self, other):
        return self.id == getattr(other, 'id', None)

    def __hash__(self):
        return id(self)

    def __ne__(self, other):
        return self.id != getattr(other, 'id', None)

    @cached_property
    def accepted_talks(self):
        """Return the user's accepted talks."""
        return Talk.query.current.filter(Talk.status == 'accepted',
                                         Talk.user == self).order_by(Talk.name)

    @cached_property
    def has_accepted_talks(self):
        """Return whether the user has accepted talks."""
        return self.accepted_talks.count() > 0

    @cached_property
    def is_volunteer(self):
        """Return whether the user has signed up to volunteer."""
        volunteers = Volunteer.query.filter(
            Volunteer.event_id == g.current_event.id,
            Volunteer.user_id == self.id,
        )

        return db.session.query(volunteers.exists()).scalar()
Example #16
0
import string

from cached_property import cached_property
from flask import g
from flask_security import RoleMixin, UserMixin, recoverable
from sqlalchemy import event

from pygotham.core import db
from pygotham.events.models import Volunteer
from pygotham.talks.models import Talk

__all__ = ('Role', 'User')

roles_users = db.Table(
    'roles_users',
    db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('roles.id')),
)


class Role(db.Model, RoleMixin):
    """User role."""

    __tablename__ = 'roles'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True)
    description = db.Column(db.String(255))

    def __str__(self):
        """Return a printable representation."""
Example #17
0
class Talk(db.Model):
    """Talk."""

    __tablename__ = 'talks'
    query_class = EventQuery

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text, nullable=False)
    status = db.Column(
        db.Enum('draft', 'submitted', 'accepted', 'rejected', name='status'),
        default='draft',
        nullable=False,
    )
    level = db.Column(
        db.Enum('novice', 'intermediate', 'advanced', name='level'),
        nullable=False,
    )
    type = db.Column(
        db.Enum('talk', 'tutorial', name='type'),
        nullable=False,
    )
    duration_id = db.Column(db.ForeignKey('durations.id'), nullable=False)
    duration = db.relationship('Duration')
    recording_release = db.Column(db.Boolean, nullable=True)

    abstract = db.Column(db.Text)
    additional_requirements = db.Column(db.Text)
    objectives = db.Column(db.Text)
    outline = db.Column(db.Text)
    target_audience = db.Column(db.Text)

    event_id = db.Column(
        db.Integer,
        db.ForeignKey('events.id'),
        nullable=False,
    )
    event = db.relationship(
        'Event',
        backref=db.backref('talks', lazy='dynamic'),
    )

    category_id = db.Column(db.Integer,
                            db.ForeignKey('categories.id'),
                            nullable=True)
    category = db.relationship(
        'Category',
        backref=db.backref('talks', lazy='dynamic'),
    )

    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    user = db.relationship('User', backref=db.backref('talks', lazy='dynamic'))

    video_url = db.Column(db.String(255))

    def __str__(self):
        """Return a printable representation."""
        return self.name

    @property
    def is_accepted(self):
        """Return whether the instance is accepted."""
        return self.status == 'accepted'

    @property
    def slug(self):
        """Return a slug for the instance."""
        return slugify(self.name, max_length=25)
Example #18
0
class Day(db.Model):
    """Day of talks."""

    __tablename__ = 'days'

    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.Date)
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.id'),
                         nullable=False)
    event = db.relationship('Event',
                            backref=db.backref('days', lazy='dynamic'))

    def __str__(self):
        """Return a printable representation."""
        return self.date.strftime('%B %d, %Y')

    @cached_property
    def rooms(self):
        """Return the rooms for the day."""
        return Room.query.join(rooms_slots,
                               Slot).filter(Slot.day == self).order_by(
                                   Room.order).all()

    def __iter__(self):
        """Iterate over the schedule for the day."""
        if not self.rooms:
            raise StopIteration

        def rowspan(start, end):
            """Find the rowspan for an entry in the schedule table.

            This uses a binary search for the given end time from a
            sorted list of start times in order to find the index of the
            first start time that occurs after the given end time. This
            method is used to prevent issues that can occur with
            overlapping start and end times being included in the same
            list.
            """
            return bisect_left(times, end) - times.index(start)

        times = sorted({slot.start for slot in self.slots})
        # While we typically only care about the start times here, the
        # list is iterated over two items at a time. Without adding a
        # final element, the last time slot would be omitted. Any value
        # could be used here as bisect_left only assumes the list is
        # sorted, but using a meaningful value feels better.
        times.append(self.slots[-1].end)

        slots = db.session.query(
            Slot.id,
            Slot.content_override,
            Slot.kind,
            Slot.start,
            Slot.end,
            func.count(rooms_slots.c.slot_id).label('room_count'),
            func.min(Room.order).label('order'),
        ).join(rooms_slots, Room).filter(Slot.day == self).order_by(
            func.count(rooms_slots.c.slot_id),
            func.min(Room.order)).group_by(Slot.id, Slot.content_override,
                                           Slot.kind, Slot.start,
                                           Slot.end).all()

        for time, next_time in pairwise(times):
            row = {'time': time, 'slots': []}
            for slot in slots:
                if slot.start == time:
                    slot.rowspan = rowspan(slot.start, slot.end)
                    slot.colspan = slot.room_count
                    if not slot.content_override:
                        slot.presentation = Presentation.query.filter(
                            Presentation.slot_id == slot.id).first()
                    row['slots'].append(slot)
            if row['slots'] or next_time is None:
                yield row