示例#1
0
class PasswordReset(db.Model):
    """Model for password reset key"""
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    key = db.Column(db.String(64))
    expiration = db.Column(db.DateTime)

    def __init__(self, user, key=None):
        """Initialize a  model"""
        if key is None:
            key = ''.join(
                random.choice(string.ascii_letters + string.digits)
                for _ in range(60))
        self.user = user
        self.key = key
        # Add one additional day so the user can potentially reset their password at midnight
        self.expiration = datetime.datetime.now() + datetime.timedelta(days=8)

    def __repr__(self):
        """Return a descriptive representation of password reset"""
        return '<Reset password for user %r>' % self.user

    @classmethod
    def get_by_key(cls, key):
        """Retrieve a user by the associated password reset key"""
        pw_reset = PasswordReset.query.filter_by(key=key).first()
        if pw_reset is not None:
            return pw_reset.user
        else:
            return None
示例#2
0
class ErrorReport(db.Model):
    """Class representing bug reports and feature requests"""
    id = db.Column(db.Integer, primary_key=True)
    is_bug = db.Column(db.Boolean)
    error_text = db.Column(db.Text)
    user_text = db.Column(db.Text)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    sent = db.Column(db.Boolean)
    time = db.Column(db.DateTime)

    def __init__(self, is_bug, error_text, user_text, user):
        """Initialize the ErrorReport model"""
        self.is_bug = is_bug
        self.error_text = error_text
        self.user_text = user_text
        self.user = user
        self.sent = False
        self.time = datetime.datetime.now()

    @property
    def email_format(self):
        """Format the ErrorReport neatly for emailing purposes"""
        this_type = 'Bug Report' if self.is_bug else 'Feature Request'
        error_text = ''
        if self.error_text:
            error_text = 'Error: {text}'.format(text=self.error_text)
        return "{id}. {type} sent by {user} at {time}\n{user_text}\n{error_text}\n".format(
            id=self.id,
            type=this_type,
            user=self.user.name,
            time=self.time.strftime('%I:%M%p on %Y-%m-%d'),
            user_text=self.user_text,
            error_text=error_text,
        )

    def __repr__(self):
        """Return a human-readable representation of this ErrorReport"""
        this_type = 'Bug Report' if self.is_bug else 'Feature Request'
        return '<ErrorReport #{id} ({type})>'.format(id=self.id,
                                                     type=this_type)

    @classmethod
    def send_new(cls):
        """Send new ErrorReports to the project developers"""
        error_reports = cls.query.filter_by(sent=False).all()
        today = datetime.datetime.today().strftime('%Y-%m-%d')

        if error_reports:
            title = 'Bug Reports and Feature Requests {}'.format(today)
            msg = Message(title,
                          recipients=[os.environ['DLI_REPORTS_DEV_EMAIL']])
            msg.body = '\n\n'.join(er.email_format for er in error_reports)
            mail.send(msg)
            for er in error_reports:
                er.sent = True
            db.session.commit()
示例#3
0
class Field(db.Model):
    """Model for a Field within a Report"""
    __tablename__ = "field"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32))
    ftype_id = db.Column(db.Integer, db.ForeignKey("field_type.id"))
    ftype = db.relationship(FieldType)
    department_id = db.Column(db.Integer, db.ForeignKey("department.id"))
    data_points = db.relationship(
        FieldData,
        backref='field',
        lazy='dynamic',
    )

    def __init__(self, name, ftype, department):
        """Initialize a Field model"""
        self.name = name
        self.ftype = ftype
        self.department = department

    def __repr__(self):
        """Return a descriptive representation of a Field"""
        return '<Field %r>' % self.name

    def get_data_for_date(self, ds, pretty=False):
        """Retrieve the FieldData instance for the given date stamp"""
        data_point = self.data_points.filter_by(ds=ds).first()
        if pretty:
            if data_point is not None:
                data_point = data_point.pretty_value
            else:
                data_point = ""
        return data_point

    @property
    def identifier(self):
        """Property to uniquely identify this Field"""
        return '{}: {}'.format(self.department.name, self.name)
示例#4
0
class Chart(db.Model):
    """Model for a DLI Chart"""
    __tablename__ = "chart"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), index=True)
    with_table = db.Column(db.Boolean)
    owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
    ctype_id = db.Column(db.Integer, db.ForeignKey("chart_type.id"))
    ctype = db.relationship(ChartType, backref="charts")
    fields = db.relationship(
        Field,
        secondary=chart_fields,
        backref='charts',
    )
    tags = db.relationship(
        Tag,
        secondary=chart_tags,
        backref='charts',
    )

    def __init__(self, name, with_table, user, ctype, fields, tags):
        """Initialize a Chart model"""
        self.name = name
        self.with_table = with_table
        self.user = user
        self.ctype = ctype
        self.fields = fields
        self.tags = tags

    def __repr__(self):
        """Return a descriptive representation of a Chart"""
        return '<Chart %r>' % self.name

    @property
    def is_pie_chart(self):
        """Return whether or not this chart is a Pie Chart"""
        ChartTypeConstants.reload()
        return self.ctype == ChartTypeConstants.PIE

    def data_points(self, min_date, max_date=None, ds_format=False):
        """Retrieve the data points needed for this chart"""
        if ds_format:
            min_ds = min_date
            max_ds = max_date
        else:
            min_ds = min_date.strftime('%Y-%m-%d')
            max_ds = datetime.datetime.now().strftime('%Y-%m-%d')
            if max_date:
                max_ds = max_date.strftime('%Y-%m-%d')

        return {
            field.identifier: {
                str(fdata.ds): str(fdata.value)
                for fdata in field.data_points.filter(
                    FieldData.ds >= min_ds).filter(FieldData.ds <= max_ds)
            }
            for field in self.fields
        }

    @property
    def tagnames(self):
        """Helper function to get the names of the Report's tags"""
        return [tag.name for tag in self.tags]

    def generated_js(self):
        """Property that represents this chart generated as C3 JavaScript"""
        min_date = datetime.datetime.now()
        ChartTypeConstants.reload()
        if self.ctype != ChartTypeConstants.PIE:
            min_date = min_date - datetime.timedelta(days=14)

        generate = 'true'
        if self.ctype == ChartTypeConstants.TABLE_ONLY:
            generate = 'false'

        return """
            var data_points = {data_points};
            var time_series = {time_series};
            var chart_type = "{chart_type}";
            var generate = {should_generate};
        """.format(
            time_series=get_time_series_sequence(min_date),
            data_points=self.data_points(min_date),
            chart_type=self.ctype.name,
            should_generate=generate,
        )
示例#5
0
class Report(db.Model):
    """Model for a DLI Report"""
    __tablename__ = "report"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
    name = db.Column(db.String(128))
    fields = db.relationship(
        Field,
        secondary=report_fields,
        backref='reports',
    )
    tags = db.relationship(
        Tag,
        secondary=report_tags,
        backref='reports',
    )

    def __init__(self, user, name, fields, tags):
        """Initialize a Report model"""
        self.user = user
        self.name = name
        self.fields = fields
        self.tags = tags

    def __repr__(self):
        """Return a descriptive representation of a Report"""
        return '<Report %r>' % self.name

    @property
    def tagnames(self):
        """Helper function to get the names of the Report's tags"""
        return [tag.name for tag in self.tags]

    def generate_filename(self, start_ds, end_ds):
        """Generate the filename for the Excel sheet for downloads"""
        return "{filename}-{start}-to-{end}.xlsx".format(
            filename=self.name,
            start=start_ds,
            end=end_ds,
        )

    def collect_dept_data_for_template(self, ds):
        """Collect all of the department data for this Report

        Collect department data for this Report on a given day in a format
        that is easy to template for render_template functions in Jinja2
        """

        dept_data = collections.defaultdict(list)
        for field in self.fields:
            dept_data[field.department.name].append({
                'name':
                field.name,
                'value':
                field.get_data_for_date(ds, pretty=True),
            })
        return dept_data

    def excel_filepath_for_ds(self, start_ds, end_ds):
        """Return the absolute filepath for the Excel sheet on the given ds"""
        return os.path.join(
            os.path.abspath(os.path.dirname(__file__)),
            EXCEL_FILE_DIR,
            self.generate_filename(start_ds, end_ds),
        )

    def excel_file_exists(self, start_ds, end_ds):
        """Determine whether or not an Excel file for this ds exists"""
        return os.path.exists(self.excel_filepath_for_ds(start_ds, end_ds))

    def create_excel_file(self, start_ds, end_ds):
        """Generate an Excel sheet with this Report's data

        Arguments:
        start_ds - Date stamp for the start day of Report data to generate
        end_ds - Date stamp for the end day of Report data to generate
        """

        excel_helper = ExcelSheetHelper(
            filepath=self.excel_filepath_for_ds(start_ds, end_ds),
            report=self,
            date_list=generate_date_list(
                datetime.datetime.strptime(start_ds, '%Y-%m-%d'),
                datetime.datetime.strptime(end_ds, '%Y-%m-%d'),
                step=1,
            ),
        )
        excel_helper.write_all(self.collect_dept_fields(start_ds, end_ds))
        excel_helper.finalize()

    def remove_excel_files(self):
        """Delete the Excel files for this Report"""
        basepath = os.path.join(
            os.path.abspath(os.path.dirname(__file__)),
            EXCEL_FILE_DIR,
        )
        globpath = os.path.join(basepath, self.name + '*.xlsx')

        for filename in glob.glob(globpath):
            os.remove(os.path.join(basepath, filename))

    def collect_dept_fields(self, start_ds, end_ds):
        """Collect all of the department data for this Report

        The best way we can do this is to create a complex dict:
        {dept : {field : {ds : value}}}

        Arguments:
        start_ds - the beginning ds for data to collect
        end_ds - the ending ds for data to collect
        """
        dept_data = {}
        for field in self.fields:
            field_data = {
                pt.ds: pt.value
                for pt in field.data_points.filter(
                    FieldData.ds >= start_ds).filter(FieldData.ds <= end_ds)
            }
            if not dept_data.get(field.department.name):
                dept_data[field.department.name] = {}
            dept_data[field.department.name][field] = field_data
        return dept_data
示例#6
0
class FieldData(db.Model):
    """Model for the actual data stored in a Field"""
    __tablename__ = "field_data"
    id = db.Column(db.Integer, primary_key=True)
    ds = db.Column(db.String(16), index=True)
    field_id = db.Column(db.Integer, db.ForeignKey("field.id"))
    ivalue = db.Column(db.BigInteger)
    dvalue = db.Column(db.Float)
    svalue = db.Column(db.String(128))

    def __init__(self, ds, field, value):
        """Initialize a FieldData model"""
        FieldTypeConstants.reload()
        self.ds = ds
        self.field = field

        # Type checking should have already been done from the form
        if self.field.ftype == FieldTypeConstants.CURRENCY:
            parts = value.replace(',', '').replace('$', '').split('.')
            # Convert the value into cents to avoid any floating-point issues
            self.ivalue = int(parts[0]) * 100
            if len(parts) == 2:
                self.ivalue += int(parts[1])
        elif self.field.ftype == FieldTypeConstants.DOUBLE:
            self.dvalue = value
        elif self.field.ftype == FieldTypeConstants.INTEGER:
            self.ivalue = value
        elif self.field.ftype == FieldTypeConstants.STRING:
            self.svalue = value
        elif self.field.ftype == FieldTypeConstants.TIME:
            # Convert the value into seconds for convenience
            if ':' in value:
                parts = value.split(':')
            elif '.' in value:
                # Some people use '.' to denote minutes/seconds
                parts = value.split('.')
            else:
                # If no : or ., assume the value listed is seconds
                parts = ['0', value]

            # If the user listed something like ':00', make sure we can still parse
            parts = [x or '0' for x in parts]
            self.ivalue = int(parts[0]) * 60
            if len(parts) == 2:
                self.ivalue += int(parts[1])

    def __repr__(self):
        """Return a descriptive representation of a FieldData"""
        return '<FieldData of %r>' % self.field.name

    @property
    def value(self):
        """Property to easily retrieve the FieldData's value"""
        FieldTypeConstants.reload()
        ftype = self.field.ftype
        if ftype == FieldTypeConstants.CURRENCY:
            return float(self.ivalue) / 100
        elif ftype == FieldTypeConstants.DOUBLE:
            return self.dvalue
        elif ftype == FieldTypeConstants.INTEGER:
            return self.ivalue
        elif ftype == FieldTypeConstants.STRING:
            return self.svalue
        elif ftype == FieldTypeConstants.TIME:
            return self.ivalue
        else:
            raise NotImplementedError("ERROR: Type %s not supported!" % ftype)

    @property
    def pretty_value(self):
        """Property to easily retrieve a human-readable FieldData model"""
        FieldTypeConstants.reload()
        ftype = self.field.ftype
        if ftype == FieldTypeConstants.CURRENCY:
            dollars = self.ivalue / 100
            cents = self.ivalue % 100
            return "${dollars}.{cents:02d}".format(
                dollars=dollars,
                cents=cents,
            )
        elif ftype == FieldTypeConstants.DOUBLE:
            return self.dvalue
        elif ftype == FieldTypeConstants.INTEGER:
            return self.ivalue
        elif ftype == FieldTypeConstants.STRING:
            return self.svalue
        elif ftype == FieldTypeConstants.TIME:
            mins = self.ivalue / 60
            secs = self.ivalue % 60
            return "{mins}:{secs:02d}".format(
                mins=mins,
                secs=secs,
            )
        else:
            raise NotImplementedError("ERROR: Type %s not supported!" % ftype)
示例#7
0
"""

import collections
import datetime
import glob
import os

import xlsxwriter

from dli_app import db

EXCEL_FILE_DIR = "excel-files"

report_fields = db.Table(
    'report_fields',
    db.Column('report_id', db.Integer, db.ForeignKey('report.id')),
    db.Column('field_id', db.Integer, db.ForeignKey('field.id')),
)

report_tags = db.Table(
    'report_tags',
    db.Column('report_id', db.Integer, db.ForeignKey('report.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
)

chart_fields = db.Table(
    'chart_fields',
    db.Column('chart_id', db.Integer, db.ForeignKey('chart.id')),
    db.Column('field_id', db.Integer, db.ForeignKey('field.id')),
)
示例#8
0
class User(db.Model, UserMixin):
    """Model for users of the site"""
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64))
    email = db.Column(db.String(64), index=True, unique=True)
    password = db.Column(db.String(128))
    is_admin = db.Column(db.Boolean)
    location_id = db.Column(db.Integer, db.ForeignKey("location.id"))
    dept_id = db.Column(db.Integer, db.ForeignKey("department.id"))
    pw_reset = db.relationship(
        PasswordReset,
        backref="user",
    )
    reports = db.relationship(
        Report,
        backref="user",
    )
    charts = db.relationship(
        Chart,
        backref="user",
    )
    error_reports = db.relationship(
        ErrorReport,
        backref="user",
    )
    favorite_reports = db.relationship(
        Report,
        secondary=report_users,
        backref='favorite_users',
    )
    favorite_charts = db.relationship(
        Chart,
        secondary=chart_users,
        backref='favorite_users',
    )

    def __init__(self, name, email, password, location, department):
        """Initialize a User model"""
        UserMixin.__init__(self)
        self.name = name
        self.email = email
        self.password = generate_password_hash(password)
        self.location = location
        self.department = department
        self.is_admin = False

    def set_password(self, new_password):
        """Change the user's password to the new password"""
        self.password = generate_password_hash(new_password)

    def check_password(self, password):
        """Check the user's password against the given value"""
        return check_password_hash(self.password, password)

    def favorite(self, report):
        """Add a report to the user's list of favorite reports"""
        if report not in self.favorite_reports:
            self.favorite_reports.append(report)

    def unfavorite(self, report):
        """Remove a report from the user's list of favorite reports"""
        if report in self.favorite_reports:
            self.favorite_reports.remove(report)

    def favorite_chart(self, chart):
        """Add a chart to the user's list of favorite charts"""
        if chart not in self.favorite_charts:
            self.favorite_charts.append(chart)

    def unfavorite_chart(self, chart):
        """Remove a chart from the user's list of favorite charts"""
        if chart in self.favorite_charts:
            self.favorite_charts.remove(chart)

    def __repr__(self):
        """Return a descriptive representation of a User"""
        return '<User %r>' % self.email

    @classmethod
    def get_by_email(cls, email):
        """Retrieve a user by their email address"""
        return User.query.filter_by(email=email).first()
示例#9
0
from werkzeug.security import check_password_hash
from werkzeug.security import generate_password_hash

from dli_app import db
from dli_app import login_manager
from dli_app import mail

from dli_app.mod_admin.models import ErrorReport

from dli_app.mod_reports.models import Chart
from dli_app.mod_reports.models import Field
from dli_app.mod_reports.models import Report

report_users = db.Table(
    'report_users',
    db.Column('report_id', db.Integer, db.ForeignKey('report.id')),
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
)

chart_users = db.Table(
    'chart_users',
    db.Column('chart_id', db.Integer, db.ForeignKey('chart.id')),
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
)


@login_manager.user_loader
def user_loader(user_id):
    """Unique user loader for the login manager"""
    return User.query.get(user_id)