class User(UserMixin, SurrogatePK, TimeStamped, Confirmable, Model):
    __tablename__ = 'users'

    email = Column(db.String(255), unique=True, nullable=False)
    password = Column(db.String(255), nullable=True)
    first_name = Column(db.String(255), nullable=True)
    last_name = Column(db.String(255), nullable=True)

    def __init__(self, email, password=None, **kwargs):
        db.Model.__init__(self, email=email, **kwargs)
        if password:
            self.set_password(password)
        else:
            self.password = None

    def set_password(self, password):
        self.password = bcrypt.generate_password_hash(password)

    def check_password(self, value):
        return bcrypt.check_password_hash(self.password, value)

    @property
    def full_name(self):
        if self.first_name and self.last_name:
            return "{0} {1}".format(self.first_name, self.last_name)

        return ""

    @property
    def full_name_or_email(self):
        return self.full_name or self.email

    def __repr__(self):
        return '{0} <{1}>'.format(self.full_name, self.email)

    @classmethod
    def sessionstore_user(cls):
        """
        A dummy user object to represent the logged-in user when
        we're set to 'sessionstore' instead of 'db'.
        """

        return cls(
            email=app.config['SESSIONSTORE_USER_EMAIL'],
            first_name=app.config['SESSIONSTORE_USER_FIRST_NAME'],
            last_name=app.config['SESSIONSTORE_USER_LAST_NAME'],
            active=True) class ShortTextContentBlock(SurrogatePK, Slugged, TimeStamped,
                            Confirmable, Model):
    __tablename__ = 'short_text_content_block'

    content = Column(db.String(255), nullable=False, default='')

    __table_args__ = (
        db.UniqueConstraint('slug', name='_stcb_slug_uc'),
        db.Index('_stcb_slug_active_ix', 'slug', 'active'))

    def __repr__(self):
        return self.title

    @classmethod
    def default_content(cls):
        ret = {}

        title = 'Site welcome prefix'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug,
                        content='Welcome to', active=True)

        title = 'Site byline'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug, content=(
            'A template for building a small marketing web site '
            'in Flask where all content is live editable.'),
            active=True)

        title = 'Site byline link title'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug,
                        content='Learn more', active=True)

        title = 'Site byline link URL'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(
            title=title, slug=slug,
            content='https://github.com/Jaza/flask-editablesite',
            active=True)

        title = 'About title'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug,
                        content='About', active=True)

        title = 'Gallery title'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug,
                        content='Gallery', active=True)

        title = 'Events title'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug,
                        content='Events', active=True)

        title = 'Contact title'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug,
                        content='Contact', active=True)

        return ret class RichTextContentBlock(SurrogatePK, Slugged, TimeStamped,
                           Confirmable, Model):
    __tablename__ = 'rich_text_content_block'

    content = Column(db.Text(), nullable=False, default='')

    __table_args__ = (
        db.UniqueConstraint('slug', name='_rtcb_slug_uc'),
        db.Index('_rtcb_slug_active_ix', 'slug', 'active'))

    def __repr__(self):
        return self.title

    @classmethod
    def default_content(cls):
        ret = {}

        title = 'About text (left column)'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug, content=(
            '<p>The aim of this app is to demonstrate that, with the '
            'help of modern JS libraries, and with some well-'
            'thought-out server-side snippets, it\'s now perfectly '
            'possible to "bake in" live in-place editing for '
            'virtually every content element in a typical '
            'brochureware site.</p>'),
            active=True)

        title = 'About text (right column)'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(title=title, slug=slug, content=(
            '<p>This app is not a CMS. On the contrary, think of it '
            'as a proof-of-concept alternative to a CMS. An '
            'alternative where there\'s no "admin area", there\'s '
            'no "editing mode", and there\'s no "preview '
            'button".</p>'),
            active=True)

        title = 'About text (below columns)'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(
            title=title, slug=slug,
            content="<p>There's only direct manipulation.</p>",
            active=True)

        return ret class ImageContentBlock(SurrogatePK, Slugged, TimeStamped,
                        Confirmable, Model):
    __tablename__ = 'image_content_block'

    image = Column(db.String(255), nullable=False, default='')

    __table_args__ = (
        db.UniqueConstraint('slug', name='_icb_slug_uc'),
        db.Index('_icb_slug_active_ix', 'slug', 'active'))

    def __repr__(self):
        return self.title

    @property
    def image_path(self):
        return (
            self.image
            and '%s%s' % (
                app.config['UPLOADS_RELATIVE_PATH'], self.image)
            or None)

    @property
    def image_url(self):
        if not self.image:
            return None

        return url_for('static', filename=self.image_path,
                       _external=True)

    @property
    def image_or_placeholder(self):
        return (
            self.image
            or app.config['EDITABLE_PLACEHOLDER_IMAGE_RELATIVE_PATH'])

    @classmethod
    def default_content(cls):
        ret = {}

        title = 'Site logo'
        slug = slugify(title, to_lower=True)
        ret[slug] = cls(
            title=title, slug=slug,
            image=app.config[
                'EDITABLE_PLACEHOLDER_IMAGE_RELATIVE_PATH'],
            active=True)

        return ret class GalleryItem(SurrogatePK, Slugged, TimeStamped,
                  Confirmable, Model):
    __tablename__ = 'gallery_item'

    image = Column(db.String(255), nullable=False, default='')
    content = Column(db.Text(), nullable=False, default='')
    date_taken = Column(db.String(255), nullable=False, default='')
    weight = db.Column(db.Integer(), nullable=False, default=0)

    __table_args__ = (
        db.UniqueConstraint('slug', name='_gi_slug_uc'),
        db.Index('_gi_slug_active_ix', 'slug', 'active'))

    def __repr__(self):
        return self.title

    @property
    def image_path(self):
        return (
            self.image
            and '%s%s' % (
                app.config['UPLOADS_RELATIVE_PATH'], self.image)
            or None)

    @property
    def image_url(self):
        if not self.image:
            return None

        return url_for('static', filename=self.image_path,
                       _external=True)

    @property
    def image_or_placeholder(self):
        return (
            self.image
            or app.config['EDITABLE_PLACEHOLDER_IMAGE_RELATIVE_PATH'])

    @classmethod
    def new_item(cls, title_prefix='New '):
        rand_str = ''.join(
            random.choice(string.ascii_lowercase) for _ in range(10))
        title = '{0}Gallery Item {1}'.format(title_prefix, rand_str)
        slug = slugify(title, to_lower=True)

        year_now = date.today().year
        date_taken = '{0} {1}'.format(
            random.choice(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                           'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']),
            random.choice([(year_now - x) for x in range(1, 11)]))

        return cls(
            title=title,
            slug=slug,
            image=app.config[
                'EDITABLE_PLACEHOLDER_IMAGE_RELATIVE_PATH'],
            date_taken=date_taken,
            content=app.config['EDITABLE_PLACEHOLDER_TEXT'],
            active=True)

    @classmethod
    def default_content(cls):
        ret = []

        for i in range(app.config['GALLERY_NUM_DEFAULT_ITEMS']):
            item = cls.new_item(title_prefix='Sample ')
            item.weight = i

            ret.append(item)

        return ret

    @classmethod
    def max_weight(cls):
        result = (cls.query
                     .with_entities(
                         func.max(cls.weight).label('max_weight'))
                     .first())

        if not (
                result and
                (type(result).__name__ in ('KeyedTuple', 'result')) and
                len(result) and
                result[0] is not None):
            return None

        return int(result[0]) class Event(SurrogatePK, Slugged, TimeStamped, Confirmable, Model):
    __tablename__ = 'event'

    start_date = Column(db.Date(), nullable=False)
    end_date = Column(db.Date())
    start_time = Column(db.Time())
    end_time = Column(db.Time())
    event_url = Column(db.String(255), nullable=False, default='')
    location_name = Column(db.String(255), nullable=False, default='')
    location_url = Column(db.String(255), nullable=False, default='')

    __table_args__ = (
        db.UniqueConstraint('slug', name='_event_slug_uc'),
        db.Index('_event_active_startdate_ix', 'active', 'start_date'))

    def __repr__(self):
        return self.title

    @classmethod
    def new_item(cls, title_prefix='New ', rand_start_date=False):
        rand_str = ''.join(
            random.choice(string.ascii_lowercase) for _ in range(10))
        title = '{0}Event {1}'.format(title_prefix, rand_str)
        slug = slugify(title, to_lower=True)

        # Make start date a random date within 5 years of today
        date_now = date.today()
        start_date = date_now

        if rand_start_date:
            fiveyears_indays = 365 * 5
            rand_delta = timedelta(
                days=random.randrange(
                    -fiveyears_indays, fiveyears_indays))
            start_date += rand_delta

        start_time = None
        # Toss a coin to see if we set a start time or not
        if bool(random.getrandbits(1)):
            rand_delta = timedelta(minutes=(15 * random.randrange(96)))
            dt_now = datetime.now()
            dt_midnighttoday = datetime(dt_now.year, dt_now.month, dt_now.day)
            start_time = (dt_midnighttoday + rand_delta).time()

        end_date = None
        end_time = None
        # Toss a coin to see if we set an end date or not
        if bool(random.getrandbits(1)):
            # Make end date 5 days or less ahead of start date
            rand_delta = timedelta(days=random.randrange(1, 6))
            end_date = start_date + rand_delta

            # Toss a coin to see if we set an end time or not
            if bool(random.getrandbits(1)):
                rand_delta = timedelta(minutes=(15 * random.randrange(96)))
                dt_now = datetime.now()
                dt_midnighttoday = datetime(
                    dt_now.year, dt_now.month, dt_now.day)
                end_time = (dt_midnighttoday + rand_delta).time()

        # Toss a coin to see if we set an event URL or not
        event_url = ''
        if bool(random.getrandbits(1)):
            event_url = random.choice(app.config['EDITABLE_SAMPLE_URLS'])

        # Toss a coin to see if we set a location name or not
        location_name = ''
        location_url = ''
        if bool(random.getrandbits(1)):
            rand_str = ''.join(
                random.choice(string.ascii_lowercase)
                for _ in range(10))
            location_name = 'Location {0}'.format(rand_str)

            # Toss a coin to see if we set a location URL or not
            if bool(random.getrandbits(1)):
                location_url = random.choice(
                    app.config['EDITABLE_SAMPLE_URLS'])

        return cls(
            title=title,
            slug=slug,
            start_date=start_date,
            end_date=end_date,
            start_time=start_time,
            end_time=end_time,
            event_url=event_url,
            location_name=location_name,
            location_url=location_url,
            active=True)

    @classmethod
    def default_content(cls):
        return [
            cls.new_item(title_prefix='Sample ', rand_start_date=True)
            for i in range(app.config['EVENT_NUM_DEFAULT_ITEMS'])] class TextEditForm(Form):
    content = TextField('Content', validators=[DataRequired()]) class TextOptionalEditForm(Form):
    content = TextField('Content', validators=[]) class LongTextEditForm(Form):
    content = TextAreaField('Content', validators=[DataRequired()]) class ImageEditForm(Form):
    image = FileField('Image', validators=[
        DataRequired(),
        FileAllowed(
            ('gif', 'jpg', 'jpeg', 'png'),
            ('Only image files (gif, jpg, png) can be uploaded '
                'for this field'))]) class ReorderForm(Form):
    items = FieldList(FormField(ReorderItemForm)) class DateEditForm(Form):
    content = DateField('Date', format='%d %b %Y', validators=[
        DataRequired()]) class DateOptionalEditForm(Form):
    content = DateField('Date', format='%d %b %Y', validators=[]) class TimeOptionalEditForm(Form):
    content = TimeField('Time', validators=[]) def placeholder_or_random_sample_image():
    """
    Gets a random sample image if possible, otherwise the default
    placeholder image.
    """

    is_sample_images_configured = (
        app.config.get('EDITABLE_SAMPLE_IMAGES_SCRAPE_URL') and
        app.config.get('EDITABLE_SAMPLE_IMAGES_RELATIVE_PATH'))

    return (is_sample_images_configured
            and random_sample_image()
            or app.config['EDITABLE_PLACEHOLDER_IMAGE_RELATIVE_PATH']) def placeholder_or_random_sample_text():
    """
    Gets a random sample chunk of text if possible, otherwise the
    default placeholder text.
    """

    is_sample_text_configured = (
        app.config.get('EDITABLE_SAMPLE_TEXT_SCRAPE_URLS') and
        True or False)

    return (is_sample_text_configured
            and random_sample_text()
            or app.config['EDITABLE_PLACEHOLDER_TEXT']) class LoginForm(Form):
    email = TextField('Email', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])

    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)
        self.user = None

    def validate(self):
        initial_validation = super(LoginForm, self).validate()
        if not initial_validation:
            return False

        # Log in with dummy credentials when we're set to 'sessionstore'
        # instead of 'db'.
        if app.config.get('USE_SESSIONSTORE_NOT_DB'):
            if self.email.data != app.config['SESSIONSTORE_USER_EMAIL']:
                self.email.errors.append('Unknown email')
                return False

            if self.password.data != app.config['SESSIONSTORE_USER_PASSWORD']:
                self.password.errors.append('Invalid password')
                return False

            self.user = User.sessionstore_user()
        # Regular login.
        else:
            self.user = User.query.filter_by(email=self.email.data).first()
            if not self.user:
                self.email.errors.append('Unknown email')
                return False

            if not self.user.check_password(self.password.data):
                self.password.errors.append('Invalid password')
                return False

            if not self.user.active:
                self.email.errors.append('User {0} not activated'.format(
                    self.user.full_name_or_email))
                return False

        return True class ContactForm(Form):
    name = TextField('Name', validators=[DataRequired()])
    email = TextField('Email Address', validators=[DataRequired(), Email()])
    phone = TextField('Phone Number', validators=[])
    message = TextAreaField('Message', validators=[DataRequired()])
from datetime import datetime, date
from flask import current_app as app
from flask import (Blueprint, request, render_template, flash, url_for,
                   redirect, session, send_from_directory)
from flask_wtf import Form
from flask_mail import Message
from flask_login import (login_user, login_required, logout_user,
                         current_user)
from sqlalchemy.exc import IntegrityError
from flask_editablesite.extensions import db, login_manager, mail
from flask_editablesite.user.models import User
from flask_editablesite.contentblock.models import (
    ShortTextContentBlock, RichTextContentBlock, ImageContentBlock)
from flask_editablesite.gallery.models import GalleryItem
from flask_editablesite.event.models import Event
from flask_editablesite.editable.forms import (
    TextEditForm, TextOptionalEditForm, LongTextEditForm,
    ImageEditForm, ReorderForm, DateEditForm,
    DateOptionalEditForm, TimeOptionalEditForm)
from flask_editablesite.editable.sample_images import (
    placeholder_or_random_sample_image,)
from flask_editablesite.editable.sample_text import (
    placeholder_or_random_sample_text,)
from flask_editablesite.public.forms import (
    LoginForm, ContactForm)
from flask_editablesite.utils import flash_errors
import os
                'date_taken': TextEditForm(content=gi.date_taken)}

            if current_user.is_authenticated():
                gallery_forms[gi.id]['delete'] = Form()

    return (
        gallery_items,
        is_gallery_showlimited,
        gallery_forms)


def get_gi_reorder_form():
    """Gets the gallery item re-ordering form."""

    gi_reorder_form = None

    if current_user.is_authenticated():
        if app.config.get('USE_SESSIONSTORE_NOT_DB'):
            items = [
                {'identifier': i, 'weight': i-1}
                for i, v in enumerate(session['gallery_item']) if v]
        else:
            items = [
                {'identifier': gi.id, 'weight': gi.weight}
                for gi in (
                    GalleryItem.query
                               .filter_by(active=True)
                               .order_by(GalleryItem.weight).all())]

        gi_reorder_form = ReorderForm(items=items, prefix='gallery_')

    return gi_reorder_form


def get_event_vars():
    """Gets event variables."""

    event_upcoming_limit = app.config['EVENT_UPCOMING_LIMIT']

    if app.config.get('USE_SESSIONSTORE_NOT_DB'):
        event_upcoming_count = len([
            o for o in session.get('event', [])
            if (o and (
                datetime.strptime(o['start_date'], '%Y-%m-%d').date()
                >= date.today()))])
    else:
        event_upcoming_count = (
            Event.query
                 .filter_by(active=True)
                 .filter(Event.start_date >= date.today())
                 .count())

    event_past_limit = app.config['EVENT_PAST_LIMIT']

    if app.config.get('USE_SESSIONSTORE_NOT_DB'):
        event_past_count = len([
            o for o in session.get('event', [])
            if (o and (
                datetime.strptime(o['start_date'], '%Y-%m-%d').date()
                < date.today()))])
    else:
        event_past_count = (
            Event.query
                 .filter_by(active=True)
                 .filter(Event.start_date < date.today())
                 .count())

    if (not event_upcoming_count) and (not event_past_count):
        default_events = get_default_events()

        event_upcoming_count = len([
            o for o in default_events
            if o.start_date >= date.today()])

        event_past_count = len([
            o for o in default_events
            if o.start_date < date.today()])

    is_events_showmore = (
        request.args.get('events_showmore', None) == '1')

    is_events_showlimited = (
        (not current_user.is_authenticated()) and
        (not is_events_showmore) and
        (
            (event_upcoming_count > event_upcoming_limit) or
            (event_past_count > event_past_limit)))

    events_upcoming, events_past = get_events()

    if is_events_showlimited:
        if app.config.get('USE_SESSIONSTORE_NOT_DB'):
            events_upcoming = events_upcoming[:event_upcoming_limit]
            events_past = events_past[:event_past_limit]
        else:
            events_upcoming = (
                events_upcoming.limit(event_upcoming_limit))

            events_past = (
                events_past.limit(event_past_limit))

    if not app.config.get('USE_SESSIONSTORE_NOT_DB'):
        events_upcoming = events_upcoming.all()
        events_past = events_past.all()

    event_forms = {}
    if current_user.is_authenticated():
        for event in (events_upcoming + events_past):
            event_forms[event.id] = {
                'title': TextEditForm(content=event.title),
                'event_url': TextOptionalEditForm(
                    content=event.event_url),
                'start_date': DateEditForm(
                    content=event.start_date),
                'end_date': DateOptionalEditForm(
                    content=event.end_date),
                'start_time': TimeOptionalEditForm(
                    content=event.start_time),
                'end_time': TimeOptionalEditForm(
                    content=event.end_time),

                'location_name': TextOptionalEditForm(