<commit_before>"""Data Models."""

#pylint: disable=no-member
#pylint: disable=unused-argument
#pylint: disable=too-many-return-statements

import datetime

from app import app
from app.constants import STUDENT_ROLE, STAFF_ROLE, VALID_ROLES
from app.exceptions import *
from flask import json
from flask.json import JSONEncoder as old_json

from google.appengine.ext import ndb

class JSONEncoder(old_json):
    """
    Wrapper class to try calling an object's to_dict() method. This allows
    us to JSONify objects coming from the ORM. Also handles dates & datetimes.
    """
    def default(self, obj):
        if isinstance(obj, ndb.Key):
            got = obj.get()
            if not got:
                return None
            return got.to_json()
        elif isinstance(obj, datetime.datetime):
            obj = convert_timezone(obj)
            return obj.strftime(app.config["GAE_DATETIME_FORMAT"])
        if isinstance(obj, ndb.Model):
            return obj.to_json()
        return super(JSONEncoder, self).default(obj)

app.json_encoder = JSONEncoder

def convert_timezone(utc_dt):
    """Convert times to Pacific time."""
    # This looks like a hack... is it even right? What about daylight savings?
    # Correct approach: each course should have a timezone. All times should be
    # stored in UTC for easy comparison. Dates should be converted to
    # course-local time when displayed.
    delta = datetime.timedelta(hours=-7)
    return datetime.datetime.combine(utc_dt.date(), utc_dt.time()) + delta


class Base(ndb.Model):
    """Shared utility methods and properties."""
    created = ndb.DateTimeProperty(auto_now_add=True)

    @classmethod
    def from_dict(cls, values):
        """Creates an instance from the given values."""
        inst = cls()
        inst.populate(**values)
        return inst

    @classmethod
    def _get_kind(cls):
        return cls.__name__ + 'v2'

    def to_json(self, fields=None):
        """Converts this model to a json dictionary."""
        if fields == True:
            return self.to_dict()
        elif fields == False:
            return {}

        if not fields:
            fields = {}
        if fields:
            result = self.to_dict(include=fields.keys())
        else:
            result = self.to_dict()

        if self.key and (not fields or 'id' in fields):
            result['id'] = self.key.id()

        for key, value in result.items():
            if isinstance(value, ndb.Key):
                value = value.get()
                if value:
                    result[key] = value.to_json(fields.get(key))
                else:
                    result[key] = None
            elif isinstance(value, list) and len(value) > 0 and isinstance(value[0], ndb.Key):
                new_list = []
                for value in value:
                    fields_key = fields.get(key)
                    if fields_key and not isinstance(fields_key, dict):
                        if fields.get(key):
                            new_list.append(value)
                    else:
                        value = value.get()
                        if value:
                            new_list.append(value.to_json(fields.get(key)))
                result[key] = new_list
            else:
                try:
                    new_value = app.json_encoder().default(value)
                    result[key] = new_value
                except TypeError:
                    pass
        return result

    @classmethod
    def can(cls, user, need, obj=None, query=None):
        """Whether user satisfies the given need for this object.

        The index action requires a query that gets filtered and returned.
        """
        if need.action == "index":
            assert query, "No query for index"
        need.set_object(obj or cls)
        return cls._can(user, need, obj, query)

    @classmethod
    def _can(cls, user, need, obj, query):
        """
        The internal permissions method. Overridden by subclasses.
        """
        return False


class User(Base):
    """Users may have multiple email addresses. Note: the built-in user model
    in appengine associates a different user object with each email.
    """
    email = ndb.StringProperty(repeated=True)
    is_admin = ndb.BooleanProperty(default=False)
    # TODO add a name
    # TODO add a student ID

    @property
    def logged_in(self):
        return self.email != ["_anon"]

    def append_email(self, email):
        if email not in self.email:
            self.email.append(email)

    def delete_email(self, email):
        if email in self.email and len(self.email) > 1:
            self.email.remove(email)

    def get_final_submission(self, assignment):
        query = Group.query(Group.member == self.key)
        group = query.filter(Group.assignment == assignment)
        group = group.get()
        if group == None:
            return FinalSubmission.query(FinalSubmission.submitter == self.key).get()
        else:
            return FinalSubmission.query(FinalSubmission.group == group.key).get()

    def _contains_files(self, backup):
        messages = backup.get_messages()
        if 'file_contents' in messages:
            return not messages['file_contents']

    def get_backups(self, assignment, num_backups=10):
        group = self.get_group(assignment)
        all_backups = []
        if not group:
            members = [self.key]
        else:
            members = group.member

        for member in members:
            all_backups += list(Backup.query( \
                Backup.submitter == member).filter( \
                    Backup.assignment == assignment).fetch())

        all_backups = [x for x in all_backups if self._contains_files(x)]

        all_backups.sort(lambda x, y: int(5*(int(x.created > y.created) - 0.5)))
        
        return all_backups[:num_backups]

    def get_submissions(self, assignment, num_submissions=10):
        group = self.get_group(assignment)
        all_subms = []
        if not group:
            members = [self.key]
        else:
            members = group.member

        all_submissions = []
        for member in members:
            all_submissions += list(Submission.query(Submission.submitter == member).fetch())

        all_subms = [x for x in all_submissions if x.backup.get().assignment == assignment \
                and self._contains_files(x.backup.get())]

        all_subms.sort(lambda x, y: int(5*(int(x.created > y.created) - 0.5)))

        for subm in all_subms[:num_submissions]:
            subm.messages = None

        return all_subms[:num_submissions]

    def get_group(self, assignment):
        query = Group.query(Group.member == self.key)
        group = query.filter(Group.assignment == assignment).get()
        if not group: # Might be invited to a group
            query = Group.query(Group.invited == self.key)
            group = query.filter(Group.assignment == assignment).get()
        return group

    def get_course_info(self, course):
        if not course:
            raise BadValueError("Invalid course")

        info = {'user': self}
        info['assignments'] = []

        for assignment in course.assignments:
            assign_info = {}
            group = self.get_group(assignment.key)
            assign_info['group'] = {'group_info': group, 'invited': group and self.key in group.invited}<commit_msg>Bug fix and use server_time instead of created<commit_after>"""Data Models."""

#pylint: disable=no-member
#pylint: disable=unused-argument
#pylint: disable=too-many-return-statements

import datetime

from app import app
from app.constants import STUDENT_ROLE, STAFF_ROLE, VALID_ROLES
from app.exceptions import *
from flask import json
from flask.json import JSONEncoder as old_json

from google.appengine.ext import ndb

class JSONEncoder(old_json):
    """
    Wrapper class to try calling an object's to_dict() method. This allows
    us to JSONify objects coming from the ORM. Also handles dates & datetimes.
    """
    def default(self, obj):
        if isinstance(obj, ndb.Key):
            got = obj.get()
            if not got:
                return None
            return got.to_json()
        elif isinstance(obj, datetime.datetime):
            obj = convert_timezone(obj)
            return obj.strftime(app.config["GAE_DATETIME_FORMAT"])
        if isinstance(obj, ndb.Model):
            return obj.to_json()
        return super(JSONEncoder, self).default(obj)

app.json_encoder = JSONEncoder

def convert_timezone(utc_dt):
    """Convert times to Pacific time."""
    # This looks like a hack... is it even right? What about daylight savings?
    # Correct approach: each course should have a timezone. All times should be
    # stored in UTC for easy comparison. Dates should be converted to
    # course-local time when displayed.
    delta = datetime.timedelta(hours=-7)
    return datetime.datetime.combine(utc_dt.date(), utc_dt.time()) + delta


class Base(ndb.Model):
    """Shared utility methods and properties."""
    created = ndb.DateTimeProperty(auto_now_add=True)

    @classmethod
    def from_dict(cls, values):
        """Creates an instance from the given values."""
        inst = cls()
        inst.populate(**values)
        return inst

    @classmethod
    def _get_kind(cls):
        return cls.__name__ + 'v2'

    def to_json(self, fields=None):
        """Converts this model to a json dictionary."""
        if fields == True:
            return self.to_dict()
        elif fields == False:
            return {}

        if not fields:
            fields = {}
        if fields:
            result = self.to_dict(include=fields.keys())
        else:
            result = self.to_dict()

        if self.key and (not fields or 'id' in fields):
            result['id'] = self.key.id()

        for key, value in result.items():
            if isinstance(value, ndb.Key):
                value = value.get()
                if value:
                    result[key] = value.to_json(fields.get(key))
                else:
                    result[key] = None
            elif isinstance(value, list) and len(value) > 0 and isinstance(value[0], ndb.Key):
                new_list = []
                for value in value:
                    fields_key = fields.get(key)
                    if fields_key and not isinstance(fields_key, dict):
                        if fields.get(key):
                            new_list.append(value)
                    else:
                        value = value.get()
                        if value:
                            new_list.append(value.to_json(fields.get(key)))
                result[key] = new_list
            else:
                try:
                    new_value = app.json_encoder().default(value)
                    result[key] = new_value
                except TypeError:
                    pass
        return result

    @classmethod
    def can(cls, user, need, obj=None, query=None):
        """Whether user satisfies the given need for this object.

        The index action requires a query that gets filtered and returned.
        """
        if need.action == "index":
            assert query, "No query for index"
        need.set_object(obj or cls)
        return cls._can(user, need, obj, query)

    @classmethod
    def _can(cls, user, need, obj, query):
        """
        The internal permissions method. Overridden by subclasses.
        """
        return False


class User(Base):
    """Users may have multiple email addresses. Note: the built-in user model
    in appengine associates a different user object with each email.
    """
    email = ndb.StringProperty(repeated=True)
    is_admin = ndb.BooleanProperty(default=False)
    # TODO add a name
    # TODO add a student ID

    @property
    def logged_in(self):
        return self.email != ["_anon"]

    def append_email(self, email):
        if email not in self.email:
            self.email.append(email)

    def delete_email(self, email):
        if email in self.email and len(self.email) > 1:
            self.email.remove(email)

    def get_final_submission(self, assignment):
        query = Group.query(Group.member == self.key)
        group = query.filter(Group.assignment == assignment)
        group = group.get()
        if group == None:
            return FinalSubmission.query(FinalSubmission.submitter == self.key).get()
        else:
            return FinalSubmission.query(FinalSubmission.group == group.key).get()

    def _contains_files(self, backup):
        messages = backup.get_messages()
        if 'file_contents' in messages:
            return messages['file_contents']

    def get_backups(self, assignment, num_backups=10):
        group = self.get_group(assignment)
        all_backups = []
        if not group:
            members = [self.key]
        else:
            members = group.member

        for member in members:
            all_backups += list(Backup.query( \
                Backup.submitter == member).filter( \
                    Backup.assignment == assignment).fetch())

        all_backups = [x for x in all_backups if self._contains_files(x)]

        all_backups.sort(lambda x, y: int(5*(int(x.server_time > y.server_time) - 0.5)))
        
        return all_backups[:num_backups]

    def get_submissions(self, assignment, num_submissions=10):
        group = self.get_group(assignment)
        all_subms = []
        if not group:
            members = [self.key]
        else:
            members = group.member

        all_submissions = []
        for member in members:
            all_submissions += list(Submission.query(Submission.submitter == member).fetch())

        all_subms = [x for x in all_submissions if x.backup.get().assignment == assignment \
                and self._contains_files(x.backup.get())]

        all_subms.sort(lambda x, y: int(5*(int(x.server_time > y.server_time) - 0.5)))

        for subm in all_subms[:num_submissions]:
            subm.messages = None

        return all_subms[:num_submissions]

    def get_group(self, assignment):
        query = Group.query(Group.member == self.key)
        group = query.filter(Group.assignment == assignment).get()
        if not group: # Might be invited to a group
            query = Group.query(Group.invited == self.key)
            group = query.filter(Group.assignment == assignment).get()
        return group

    def get_course_info(self, course):
        if not course:
            raise BadValueError("Invalid course")

        info = {'user': self}
        info['assignments'] = []

        for assignment in course.assignments:
            assign_info = {}
            group = self.get_group(assignment.key)
            assign_info['group'] = {'group_info': group, 'invited': group and self.key in group.invited}
{'fraction_non_alphanumeric': 0.1000738800456713, 'fraction_numerical': 0.0020149103364900263, 'mean_word_length': 2.043122828530554, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5590510', 'n_tokens_mistral': 4200, 'n_tokens_neox': 4063, 'n_words': 1163}