Ejemplo n.º 1
0
class ObjectClass(models.Model):

    DIRECT_REIMBURSABLE = DirectReimbursableConstants
    MAJOR_OBJECT_CLASS = MajorObjectClassConstants

    major_object_class = models.TextField(db_index=True,
                                          choices=MAJOR_OBJECT_CLASS.CHOICES)
    major_object_class_name = models.TextField(
        choices=MAJOR_OBJECT_CLASS.NAME_CHOICES)
    object_class = models.TextField(db_index=True)
    object_class_name = models.TextField()
    direct_reimbursable = models.TextField(db_index=True,
                                           blank=True,
                                           null=True,
                                           choices=DIRECT_REIMBURSABLE.CHOICES)
    direct_reimbursable_name = models.TextField(
        blank=True, null=True, choices=DIRECT_REIMBURSABLE.NAME_CHOICES)
    create_date = models.DateTimeField(auto_now_add=True,
                                       blank=True,
                                       null=True)
    update_date = models.DateTimeField(auto_now=True, null=True)

    objects = CTEManager()

    class Meta:
        db_table = "object_class"
        unique_together = ("object_class", "direct_reimbursable")
Ejemplo n.º 2
0
class Editorial(models.Model):
    nombre = models.CharField(max_length=100)

    class Meta:
        managed = True
        db_table = 'libreria_editorial'

    objects = CTEManager()
Ejemplo n.º 3
0
class Activity(models.Model):
    objects = CTEManager()

    who = models.CharField(max_length=255)
    when = models.DateTimeField()
    what = models.CharField(max_length=255)

    def __str__(self):
        return '{} {} {}'.format(self.who, self.when, self.what)
Ejemplo n.º 4
0
class Apple(models.Model):
    name = models.CharField('名前', max_length=20)
    parent = models.ForeignKey('self',
                               on_delete=models.SET_NULL,
                               null=True,
                               blank=True)

    objects = CTEManager()

    class Meta:
        db_table = 'apple'
Ejemplo n.º 5
0
class TreeModel(models.Model):
    tree = CTEManager()

    class Meta:
        abstract = True

    def _get_descendants(self, cte):
        model = self.__class__
        return model.tree.filter(
            # start with current nodes
            parent=self.pk
        ).values(
            "pk",
            depth=Value(0, output_field=IntegerField()),
        ).union(
            # recursive union: get descendants
            cte.join(model, parent=cte.col.pk).values(
                "pk",
                depth=cte.col.depth + Value(1, output_field=IntegerField()),
            ),
            all=True,
        )

    def _get_ancestors(self, cte):
        model = self.__class__
        return model.tree.filter(
            # start with current node
            pk=self.pk
        ).values(
            "parent_id",
            depth=Value(0, output_field=IntegerField()),
        ).union(
            # recursive union: get ancestors
            cte.join(model, pk=cte.col.parent_id).values(
                "parent_id",
                depth=cte.col.depth - Value(1, output_field=IntegerField()),
            ),
            all=True,
        )

    def descendants(self):
        cte = With.recursive(self._get_descendants)
        model = self.__class__
        return cte.join(model.tree.all(), pk=cte.col.pk).with_cte(cte).annotate(
            depth=cte.col.depth
        ).order_by('depth')

    def ancestors(self):
        cte = With.recursive(self._get_ancestors)
        model = self.__class__
        return cte.join(model.tree.all(), pk=cte.col.parent_id).with_cte(cte).annotate(
            depth=cte.col.depth
        ).order_by('depth')
Ejemplo n.º 6
0
class Comment(DateTimeMixin, models.Model):
    owner = models.ForeignKey(User,
                              on_delete=models.CASCADE,
                              related_name='comments')
    post = models.ForeignKey(Post,
                             on_delete=models.CASCADE,
                             related_name='comments')
    text = models.CharField(max_length=200)

    objects = CTEManager()

    def __str__(self) -> str:
        return f'comment of {self.owner.username} to post {self.post_id}'
Ejemplo n.º 7
0
class PropertySale(models.Model):
    PROPERTY_TYPE_CHOICES = (
        ("D", "Detached"),
        ("S", "Semi-Detached"),
        ("T", "Terraced"),
        ("F", "Flats/Maisonettes"),
        ("O", "Other"),
    )
    objects = CTEManager()

    price = models.IntegerField()
    date_of_transfer = models.DateTimeField()
    postcode = models.CharField(max_length=8)
    property_type = models.CharField(choices=PROPERTY_TYPE_CHOICES, max_length=1)

    class Meta:
        indexes = [
            models.Index(fields=["date_of_transfer", "postcode"]),
        ]
Ejemplo n.º 8
0
class CommonFields(models.Model):
    """Base model with common fields."""

    name = models.CharField(
        _("name"),
        max_length=NAME_LENGTH,
        null=True,
    )
    html_url = models.URLField(_("URL"))
    is_tracked = models.BooleanField(_("tracked"), default=True)

    objects = CTEManager()  # noqa: WPS110

    class Meta(object):
        abstract = True

    def __str__(self):
        """Represent an instance as a string."""
        return self.name
Ejemplo n.º 9
0
class UCLManagementEntity(models.Model):
    entity = models.OneToOneField('base.Entity',
                                  verbose_name=_("entity"),
                                  related_name='uclmanagement_entity',
                                  on_delete=models.CASCADE)
    academic_responsible = models.ForeignKey(
        'base.Person',
        related_name='management_entities',
        verbose_name=_("academic_responsible"),
        on_delete=models.CASCADE)
    administrative_responsible = models.ForeignKey(
        'base.Person',
        related_name='+',
        verbose_name=_("administrative_responsible"),
        on_delete=models.CASCADE)
    contact_in_person = models.ForeignKey('base.Person',
                                          related_name='+',
                                          null=True,
                                          blank=True,
                                          verbose_name=_("contact_in_name"),
                                          on_delete=models.CASCADE)
    contact_in_email = models.EmailField(
        null=True,
        blank=True,
        verbose_name=_("email"),
    )
    contact_in_url = models.URLField(
        null=True,
        blank=True,
        verbose_name=_("portal"),
    )
    contact_out_person = models.ForeignKey('base.Person',
                                           related_name='+',
                                           null=True,
                                           blank=True,
                                           verbose_name=_("contact_out_name"),
                                           on_delete=models.CASCADE)
    contact_out_email = models.EmailField(
        null=True,
        blank=True,
        verbose_name=_("email"),
    )
    contact_out_url = models.URLField(null=True,
                                      blank=True,
                                      verbose_name=_("portal"))

    course_catalogue_text_fr = models.TextField(
        _('course_catalogue_text_fr'),
        blank=True,
        default='',
    )
    course_catalogue_text_en = models.TextField(
        _('course_catalogue_text_en'),
        blank=True,
        default='',
    )
    course_catalogue_url_fr = models.URLField(
        _('course_catalogue_url_fr'),
        blank=True,
        default='',
    )
    course_catalogue_url_en = models.URLField(
        _('course_catalogue_url_en'),
        blank=True,
        default='',
    )

    objects = CTEManager()

    def __str__(self):
        return str(self.entity)

    def is_contact_in_defined(self):
        return (self.contact_in_person_id is not None
                or self.contact_in_email is not None
                or self.contact_in_url is not None)

    def is_contact_out_defined(self):
        return (self.contact_out_person_id is not None
                or self.contact_out_email is not None
                or self.contact_out_url is not None)

    def are_contacts_defined(self):
        return (self.is_contact_in_defined() or self.is_contact_out_defined())
Ejemplo n.º 10
0
class Region(Model):
    objects = CTEManager()
    name = TextField(primary_key=True)
    parent = ForeignKey("self", null=True, on_delete=CASCADE)
Ejemplo n.º 11
0
class KeyPair(Model):
    objects = CTEManager()
    key = CharField(max_length=32)
    value = IntegerField(default=0)
    parent = ForeignKey("self", null=True, on_delete=CASCADE)
Ejemplo n.º 12
0
class AwardSearchView(BaseAwardSearchModel):
    objects = CTEManager()

    class Meta:
        managed = False
        db_table = "vw_award_search"
class TreasuryAppropriationAccount(DataSourceTrackedModel):
    """Represents a single Treasury Account Symbol (TAS)."""

    treasury_account_identifier = models.AutoField(primary_key=True)
    federal_account = models.ForeignKey("FederalAccount",
                                        models.DO_NOTHING,
                                        null=True)
    tas_rendering_label = models.TextField(blank=True, null=True)
    allocation_transfer_agency_id = models.TextField(blank=True, null=True)
    awarding_toptier_agency = models.ForeignKey(
        "references.ToptierAgency",
        models.DO_NOTHING,
        null=True,
        related_name="tas_ata",
        help_text="The toptier agency object associated with the ATA",
    )
    agency_id = models.TextField()
    funding_toptier_agency = models.ForeignKey(
        "references.ToptierAgency",
        models.DO_NOTHING,
        null=True,
        help_text=
        ("The toptier agency under which this treasury account should appear in lists and dropdowns.  Not "
         "as simple as just mapping the AID to an agency, although AID does factor into the decision.  It was "
         "recently recommended we rename this to parent toptier agency, however that is a much more involved "
         "change so we're keeping current naming for now."),
    )
    beginning_period_of_availability = models.TextField(blank=True, null=True)
    ending_period_of_availability = models.TextField(blank=True, null=True)
    availability_type_code = models.TextField(blank=True, null=True)
    availability_type_code_description = models.TextField(blank=True,
                                                          null=True)
    main_account_code = models.TextField()
    sub_account_code = models.TextField()
    account_title = models.TextField(blank=True, null=True)
    reporting_agency_id = models.TextField(blank=True, null=True)
    reporting_agency_name = models.TextField(blank=True, null=True)
    budget_bureau_code = models.TextField(blank=True, null=True)
    budget_bureau_name = models.TextField(blank=True, null=True)
    fr_entity_code = models.TextField(blank=True, null=True)
    fr_entity_description = models.TextField(blank=True, null=True)
    budget_function_code = models.TextField(blank=True, null=True)
    budget_function_title = models.TextField(blank=True, null=True)
    budget_subfunction_code = models.TextField(blank=True, null=True)
    budget_subfunction_title = models.TextField(blank=True, null=True)
    drv_appropriation_availability_period_start_date = models.DateField(
        blank=True, null=True)
    drv_appropriation_availability_period_end_date = models.DateField(
        blank=True, null=True)
    drv_appropriation_account_expired_status = models.TextField(blank=True,
                                                                null=True)
    create_date = models.DateTimeField(auto_now_add=True,
                                       blank=True,
                                       null=True)
    update_date = models.DateTimeField(auto_now=True, null=True)
    internal_start_date = models.DateField(blank=True, null=True)
    internal_end_date = models.DateField(blank=True, null=True)

    @staticmethod
    def generate_tas_rendering_label(ata, aid, typecode, bpoa, epoa, mac, sub):
        tas_rendering_label = "-".join(filter(None, (ata, aid)))

        if typecode is not None and typecode != "":
            tas_rendering_label = "-".join(
                filter(None, (tas_rendering_label, typecode)))
        else:
            poa = "/".join(filter(None, (bpoa, epoa)))
            tas_rendering_label = "-".join(
                filter(None, (tas_rendering_label, poa)))

        tas_rendering_label = "-".join(
            filter(None, (tas_rendering_label, mac, sub)))

        return tas_rendering_label

    @staticmethod
    def tas_rendering_label_to_component_dictionary(
            tas_rendering_label) -> dict:
        try:
            components = tas_rendering_label.split("-")
            if len(components) < 4 or len(components) > 5:
                raise Exception  # don't have to be specific here since this is being swallowed and replaced
            retval = {}
            # we go in reverse, since the first component is the only optional one
            retval["sub"] = components[-1]
            retval["main"] = components[-2]

            # the third component from the back can either be two years, or one character
            if len(components[-3]) > 1:
                dates = components[-3].split("/")
                retval["bpoa"] = dates[0]
                retval["epoa"] = dates[1]
            else:
                retval["a"] = components[-3]

            retval["aid"] = components[-4]

            # ata may or may not be present
            if len(components) > 4:
                retval["ata"] = components[-5]

            return retval
        except Exception:
            raise UnprocessableEntityException(
                f"Cannot parse provided TAS: {tas_rendering_label}. Valid examples: 000-2010/2010-0400-000, 009-X-1701-000, 019-011-X-1071-000"
            )

    @property
    def program_activities(self):
        return [
            pb.program_activity
            for pb in self.program_balances.distinct("program_activity")
        ]

    @property
    def object_classes(self):
        return [
            pb.object_class
            for pb in self.program_balances.distinct("object_class")
        ]

    @property
    def totals_object_class(self):
        results = []
        for object_class in self.object_classes:
            obligations = defaultdict(Decimal)
            outlays = defaultdict(Decimal)
            for pb in self.program_balances.filter(object_class=object_class):
                reporting_fiscal_year = fy(
                    pb.submission.reporting_period_start)
                obligations[
                    reporting_fiscal_year] += pb.obligations_incurred_by_program_object_class_cpe
                outlays[
                    reporting_fiscal_year] += pb.gross_outlay_amount_by_program_object_class_cpe
            result = {
                "major_object_class_code": None,
                "major_object_class_name":
                None,  # TODO: enable once ObjectClass populated
                "object_class": object_class.object_class,  # TODO: remove
                "outlays": obligations,
                "obligations": outlays,
            }
            results.append(result)
        return results

    @property
    def totals_program_activity(self):
        results = []
        for pa in self.program_activities:
            obligations = defaultdict(Decimal)
            outlays = defaultdict(Decimal)
            for pb in self.program_balances.filter(program_activity=pa):
                reporting_fiscal_year = fy(
                    pb.submission.reporting_period_start)
                # TODO: once it is present, use the reporting_fiscal_year directly
                obligations[
                    reporting_fiscal_year] += pb.obligations_incurred_by_program_object_class_cpe
                outlays[
                    reporting_fiscal_year] += pb.gross_outlay_amount_by_program_object_class_cpe
            result = {
                "id": pa.id,
                "program_activity_name": pa.program_activity_name,
                "program_activity_code": pa.program_activity_code,
                "obligations": obligations,
                "outlays": outlays,
            }
            results.append(result)
        return results

    @property
    def totals(self):
        outlays = defaultdict(Decimal)
        obligations = defaultdict(Decimal)
        budget_authority = defaultdict(Decimal)
        for ab in self.account_balances.all():
            fiscal_year = fy(ab.reporting_period_start)
            budget_authority[
                fiscal_year] += ab.budget_authority_appropriated_amount_cpe
            outlays[fiscal_year] += ab.gross_outlay_amount_by_tas_cpe
            obligations[
                fiscal_year] += ab.obligations_incurred_total_by_tas_cpe
        results = {
            "outgoing": {
                "outlays": outlays,
                "obligations": obligations,
                "budget_authority": budget_authority
            },
            "incoming": {},
        }
        return results

    objects = CTEManager()

    class Meta:
        managed = True
        db_table = "treasury_appropriation_account"

    def __str__(self):
        return self.tas_rendering_label
Ejemplo n.º 14
0
def get_manager(self, model):
    if not hasattr(model, 'cte_objects'):
        manager = CTEManager()
        manager.contribute_to_class(model, 'cte_objects')
    return model.cte_objects
class FinancialAccountsByAwards(DataSourceTrackedModel):
    financial_accounts_by_awards_id = models.AutoField(primary_key=True)
    distinct_award_key = models.TextField(db_index=True)
    treasury_account = models.ForeignKey("accounts.TreasuryAppropriationAccount", models.CASCADE, null=True)
    submission = models.ForeignKey("submissions.SubmissionAttributes", models.CASCADE)
    award = models.ForeignKey("awards.Award", models.CASCADE, null=True, related_name="financial_set")
    program_activity = models.ForeignKey("references.RefProgramActivity", models.DO_NOTHING, null=True, db_index=True)
    object_class = models.ForeignKey("references.ObjectClass", models.DO_NOTHING, null=True, db_index=True)
    piid = models.TextField(blank=True, null=True)
    parent_award_id = models.TextField(blank=True, null=True)
    fain = models.TextField(blank=True, null=True)
    uri = models.TextField(blank=True, null=True)
    disaster_emergency_fund = models.ForeignKey(
        "references.DisasterEmergencyFundCode",
        models.DO_NOTHING,
        blank=True,
        null=True,
        db_index=True,
        db_column="disaster_emergency_fund_code",
    )
    ussgl480100_undelivered_orders_obligations_unpaid_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl480100_undelivered_orders_obligations_unpaid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl483100_undelivered_orders_oblig_transferred_unpaid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl488100_upward_adjust_pri_undeliv_order_oblig_unpaid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl490100_delivered_orders_obligations_unpaid_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl490100_delivered_orders_obligations_unpaid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl493100_delivered_orders_oblig_transferred_unpaid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl498100_upward_adjust_pri_deliv_orders_oblig_unpaid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl480200_undelivered_orders_oblig_prepaid_advanced_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl480200_undelivered_orders_oblig_prepaid_advanced_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl483200_undeliv_orders_oblig_transferred_prepaid_adv_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl488200_up_adjust_pri_undeliv_order_oblig_ppaid_adv_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl490200_delivered_orders_obligations_paid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl490800_authority_outlayed_not_yet_disbursed_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl490800_authority_outlayed_not_yet_disbursed_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl498200_upward_adjust_pri_deliv_orders_oblig_paid_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    obligations_undelivered_orders_unpaid_total_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    obligations_delivered_orders_unpaid_total_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    obligations_delivered_orders_unpaid_total_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    gross_outlays_undelivered_orders_prepaid_total_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    gross_outlays_undelivered_orders_prepaid_total_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    gross_outlays_delivered_orders_paid_total_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    gross_outlay_amount_by_award_fyb = models.DecimalField(max_digits=23, decimal_places=2, blank=True, null=True)
    gross_outlay_amount_by_award_cpe = models.DecimalField(max_digits=23, decimal_places=2, blank=True, null=True)
    obligations_incurred_total_by_award_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl487100_down_adj_pri_unpaid_undel_orders_oblig_recov_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl497100_down_adj_pri_unpaid_deliv_orders_oblig_recov_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl487200_down_adj_pri_ppaid_undel_orders_oblig_refund_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    ussgl497200_down_adj_pri_paid_deliv_orders_oblig_refund_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    deobligations_recoveries_refunds_of_prior_year_by_award_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    obligations_undelivered_orders_unpaid_total_fyb = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    gross_outlays_delivered_orders_paid_total_cpe = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    drv_award_id_field_type = models.TextField(blank=True, null=True)
    drv_obligations_incurred_total_by_award = models.DecimalField(
        max_digits=23, decimal_places=2, blank=True, null=True
    )
    transaction_obligated_amount = models.DecimalField(max_digits=23, decimal_places=2, blank=True, null=True)
    reporting_period_start = models.DateField(blank=True, null=True)
    reporting_period_end = models.DateField(blank=True, null=True)
    last_modified_date = models.DateField(blank=True, null=True)
    certified_date = models.DateField(blank=True, null=True)
    create_date = models.DateTimeField(auto_now_add=True, blank=True, null=True)
    update_date = models.DateTimeField(auto_now=True, null=True)

    objects = CTEManager()

    class Meta:
        managed = True
        db_table = "financial_accounts_by_awards"
        index_together = [
            # This index dramatically sped up disaster endpoint queries.  VERY IMPORTANT!  It needs
            # to cover all of the fields being queried in order to eek out maximum performance.
            [
                "disaster_emergency_fund",
                "submission",
                "award",
                "piid",
                "fain",
                "uri",
                "parent_award_id",
                "transaction_obligated_amount",
                "gross_outlay_amount_by_award_cpe",
            ]
        ]
        indexes = [
            models.Index(
                fields=[
                    "submission",
                    "distinct_award_key",
                    "piid",
                    "transaction_obligated_amount",
                    "gross_outlay_amount_by_award_cpe",
                ],
                name="faba_subid_awardkey_sums_idx",
                condition=Q(disaster_emergency_fund__in=["L", "M", "N", "O", "P"]),
            )
        ]
Ejemplo n.º 16
0
class Order(Model):
    objects = CTEManager()
    id = AutoField(primary_key=True)
    region = ForeignKey(Region, on_delete=CASCADE)
    amount = IntegerField(default=0)
Ejemplo n.º 17
0
class Comment(models.Model):
    post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
    text = models.TextField()
    created_at = models.DateTimeField(auto_add=True)

    objects = CTEManager()  # required only for second solution
Ejemplo n.º 18
0
class Performance(AdminModel):
    '''
    Each player in each session has a Performance associated with them.

    The only input here is the partial play weighting, which models just that, early departure from the game session before the game is complete.
    But can be used to arbitrarily weight contributions of players to teams as well if desired.

    This model also contains for each session a record of the calculated Trueskill performance of that player, namely trueskill values before
    and after the play (for data redundancy as the after values of one session are the before values of the next for a given player, which can
    also be asserted for data integrity).
    '''
    objects = CTEManager()

    TS = TrueskillSettings()

    session = models.ForeignKey(
        'Session',
        verbose_name='Session',
        related_name='performances',
        on_delete=models.CASCADE
    )  # If the session is deleted, dlete this performance
    player = models.ForeignKey(
        'Player',
        verbose_name='Player',
        related_name='performances',
        null=True,
        on_delete=models.SET_NULL
    )  # if the player is deleted keep this performance

    partial_play_weighting = models.FloatField('Partial Play Weighting (ω)',
                                               default=1)

    score = models.IntegerField(
        'Score', default=None, null=True,
        blank=True)  # What this player scored if the game has scores.

    play_number = models.PositiveIntegerField(
        'The number of this play (for this player at this game)',
        default=1,
        editable=False)
    victory_count = models.PositiveIntegerField(
        'The count of victories after this session (for this player at this game)',
        default=0,
        editable=False)

    # Although Eta (η) is a simple function of Mu (µ) and Sigma (σ), we store it alongside Mu and Sigma because it is also a function of global settings µ0 and σ0.
    # To protect ourselves against changes to those global settings, or simply to detect them if it should happen, we capture their value at time of rating update in the Eta.
    # The before values are copied from the Rating for that Player/Game combo and the after values are written back to that Rating.
    trueskill_mu_before = models.FloatField(
        'Trueskill Mean (µ) before the session.',
        default=TS.mu0,
        editable=False)
    trueskill_sigma_before = models.FloatField(
        'Trueskill Standard Deviation (σ) before the session.',
        default=TS.sigma0,
        editable=False)
    trueskill_eta_before = models.FloatField(
        'Trueskill Rating (η) before the session.', default=0, editable=False)

    trueskill_mu_after = models.FloatField(
        'Trueskill Mean (µ) after the session.',
        default=TS.mu0,
        editable=False)
    trueskill_sigma_after = models.FloatField(
        'Trueskill Standard Deviation (σ) after the session.',
        default=TS.sigma0,
        editable=False)
    trueskill_eta_after = models.FloatField(
        'Trueskill Rating (η) after the session.', default=0, editable=False)

    # Record the global TrueskillSettings mu0, sigma0 and delta with each performance
    # This will allow us to reset ratings to the state they were at after this performance
    # It is an integrity measure as well against changes in these settings while a leaderboard
    # is running, which has significant consequences (suggesting a rebuild of all ratings is in
    # order)
    trueskill_mu0 = models.FloatField('Trueskill Initial Mean (µ)',
                                      default=trueskill.MU,
                                      editable=False)
    trueskill_sigma0 = models.FloatField(
        'Trueskill Initial Standard Deviation (σ)',
        default=trueskill.SIGMA,
        editable=False)
    trueskill_delta = models.FloatField('TrueSkill Delta (δ)',
                                        default=trueskill.DELTA,
                                        editable=False)

    # Record the game specific Trueskill settings beta, tau and p with each performance.
    # Again for a given game these must be consistent among all ratings and the history of each rating.
    # Any change while managing leaderboards should trigger an update request for ratings relating to this game.
    trueskill_beta = models.FloatField('TrueSkill Skill Factor (ß)',
                                       default=trueskill.BETA,
                                       editable=False)
    trueskill_tau = models.FloatField('TrueSkill Dynamics Factor (τ)',
                                      default=trueskill.TAU,
                                      editable=False)
    trueskill_p = models.FloatField('TrueSkill Draw Probability (p)',
                                    default=trueskill.DRAW_PROBABILITY,
                                    editable=False)

    Game = apps.get_model(APP, "Game", require_ready=False)
    Rank = apps.get_model(APP, "Rank", require_ready=False)
    Rating = apps.get_model(APP, "Rating", require_ready=False)

    @property
    def game(self) -> Game:
        '''
        The game this performance relates to
        '''
        return self.session.game

    @property
    def date_time(self) -> datetime:
        '''
        The game this performance relates to
        '''
        return self.session.date_time

    @property
    def rank(self) -> Rank:
        '''
        The rank of this player in this session. Most certainly a component of a player's
        performance, but not stored in the Performance model because it is associated either
        with a player or whole team depending on the play mode (Individual or Team). So this
        property fetches the rank from the Rank model where it's stored.
        '''
        Rank = apps.get_model(APP, "Rank")

        sfilter = Q(session=self.session)
        ipfilter = Q(player=self.player)
        tpfilter = Q(team__players=self.player)
        rfilter = sfilter & (ipfilter | tpfilter)
        return Rank.objects.filter(rfilter).first()

    @property
    def is_victory(self) -> bool:
        '''
        True if this performance records a victory for the player, False if not.
        '''
        return self.rank == 1

    @property
    def rating(self) -> Rating:
        '''
        Returns the rating object associated with this performance. That is for the same player/game combo.
        '''
        Rating = apps.get_model(APP, "Rating")

        try:
            r = Rating.objects.get(player=self.player, game=self.session.game)
        except Rating.DoesNotExist:
            r = Rating.create(player=self.player, game=self.session.game)
        except Rating.MultipleObjectsReturned:
            raise ValueError(
                "Database error: more than one rating for {} at {}".format(
                    self.player.name_nickname, self.session.game.name))
        return r

    @property
    def previous_play(self) -> 'Performance':
        '''
        Returns the previous performance object that this player played this game in.
        '''
        return self.session.previous_performance(self.player)

    @property
    def previous_win(self) -> 'Performance':
        '''
        Returns the previous performance object that this player played this game in and one in
        '''
        return self.session.previous_victory(self.player)

    @property
    def link_internal(self) -> str:
        return reverse('view',
                       kwargs={
                           "model": self._meta.model.__name__,
                           "pk": self.pk
                       })

    def initialise(self, save=False):
        '''
        Initialises the performance object.

        With the session and player already known,
        finds the previous session that this player played this game in
        and copies the after ratings of the previous performance (for this
        player at this session's game) to the before ratings of this
        performance object for that player.
        '''

        previous = self.session.previous_performance(self.player)

        if previous is None:
            TSS = TrueskillSettings()
            self.play_number = 1
            self.victory_count = 1 if self.session.rank(
                self.player).rank == 1 else 0
            self.trueskill_mu_before = TSS.mu0
            self.trueskill_sigma_before = TSS.sigma0
            self.trueskill_eta_before = 0

        else:
            self.play_number = previous.play_number + 1
            self.victory_count = previous.victory_count + 1 if self.session.rank(
                self.player).rank == 1 else previous.victory_count
            self.trueskill_mu_before = previous.trueskill_mu_after
            self.trueskill_sigma_before = previous.trueskill_sigma_after
            self.trueskill_eta_before = previous.trueskill_eta_after

        # Capture the Trueskill settings that are in place now too.
        TS = TrueskillSettings()
        self.trueskill_mu0 = TS.mu0
        self.trueskill_sigma0 = TS.sigma0
        self.trueskill_delta = TS.delta
        self.trueskill_beta = self.session.game.trueskill_beta
        self.trueskill_tau = self.session.game.trueskill_tau
        self.trueskill_p = self.session.game.trueskill_p

        if save:
            self.save()

    def check_integrity(self, passthru=True):
        '''
        Perform basic integrity checks on this Performance object.
        '''
        L = AssertLog(passthru)

        pfx = f"Performance Integrity error (id: {self.id}):"

        # Check that the before values match the after values of the previous play
        performance = self
        previous = self.previous_play

        if previous is None:
            TS = TrueskillSettings()

            trueskill_eta = TS.mu0 - TS.mu0 / TS.sigma0 * TS.sigma0

            L.Assert(
                isclose(performance.trueskill_mu_before,
                        TS.mu0,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance µ mismatch. Before at {performance.session.date_time} is {performance.trueskill_mu_before} and After on previous at Never is {TS.mu0} (the default)"
            )
            L.Assert(
                isclose(performance.trueskill_sigma_before,
                        TS.sigma0,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance σ mismatch. Before at {performance.session.date_time} is {performance.trueskill_sigma_before} and After on previous at Never is {TS.sigma0} (the default)"
            )
            L.Assert(
                isclose(performance.trueskill_eta_before,
                        trueskill_eta,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance η mismatch. Before at {performance.session.date_time} is {performance.trueskill_eta_before} and After on previous at Never is {trueskill_eta} (the default)"
            )
        else:
            L.Assert(
                isclose(performance.trueskill_mu_before,
                        previous.trueskill_mu_after,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance µ mismatch. Before at {performance.session.date_time} is {performance.trueskill_mu_before} and After on previous at {previous.session.date_time} is {previous.trueskill_mu_after}"
            )
            L.Assert(
                isclose(performance.trueskill_sigma_before,
                        previous.trueskill_sigma_after,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance σ mismatch. Before at {performance.session.date_time} is {performance.trueskill_sigma_before} and After on previous at {previous.session.date_time} is {previous.trueskill_sigma_after}"
            )
            L.Assert(
                isclose(performance.trueskill_eta_before,
                        previous.trueskill_eta_after,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance η mismatch. Before at {performance.session.date_time} is {performance.trueskill_eta_before} and After on previous at {previous.session.date_time} is {previous.trueskill_eta_after}"
            )

        # Check that the Trueskill settings are consistent with previous play too
        if previous is None:
            TS = TrueskillSettings()

            L.Assert(
                isclose(performance.trueskill_mu0,
                        TS.mu0,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance µ0 mismatch. At {performance.session.date_time} is {performance.trueskill_mu0} and previous at Never is {TS.mu0}"
            )
            L.Assert(
                isclose(performance.trueskill_sigma0,
                        TS.sigma0,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance σ0 mismatch. At {performance.session.date_time} is {performance.trueskill_sigma0} and on previous at Never is {TS.sigma0}"
            )
            L.Assert(
                isclose(performance.trueskill_delta,
                        TS.delta,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance δ mismatch. At {performance.session.date_time} is {performance.trueskill_delta} and previous at Never is {TS.delta}"
            )
        else:
            L.Assert(
                isclose(performance.trueskill_mu0,
                        previous.trueskill_mu0,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance µ0 mismatch. At {performance.session.date_time} is {performance.trueskill_mu0} and previous at {previous.session.date_time} is {previous.trueskill_mu0}"
            )
            L.Assert(
                isclose(performance.trueskill_sigma0,
                        previous.trueskill_sigma0,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance σ0 mismatch. At {performance.session.date_time} is {performance.trueskill_sigma0} and previous at {previous.session.date_time} is {previous.trueskill_sigma0}"
            )
            L.Assert(
                isclose(performance.trueskill_delta,
                        previous.trueskill_delta,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance δ mismatch. At {performance.session.date_time} is {performance.trueskill_delta} and previous at {previous.session.date_time} is {previous.trueskill_delta}"
            )
            L.Assert(
                isclose(performance.trueskill_beta,
                        previous.trueskill_beta,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance ß mismatch. At {performance.session.date_time} is {performance.trueskill_beta} and previous at {previous.session.date_time} is {previous.trueskill_beta}"
            )
            L.Assert(
                isclose(performance.trueskill_tau,
                        previous.trueskill_tau,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance τ mismatch. At {performance.session.date_time} is {performance.trueskill_tau} and previous at {previous.session.date_time} is {previous.trueskill_tau}"
            )
            L.Assert(
                isclose(performance.trueskill_p,
                        previous.trueskill_p,
                        abs_tol=FLOAT_TOLERANCE),
                f"{pfx} Performance p mismatch. At {performance.session.date_time} is {performance.trueskill_p} and previous at {previous.session.date_time} is {previous.trueskill_p}"
            )

        # Check that there is an associate Rank
        L.Assert(not self.rank is None, f"{pfx} Has no associated rank!")

        # if self.player.pk == 1 and self.session.game.pk == 29 and self.session.date_time.year == 2021:
        # breakpoint()

        # Check that play number and victory count reflect earlier records
        expected_play_number = self.session.previous_sessions(
            self.player).count()  # Includes the current sessions
        expected_victory_count = self.session.previous_victories(
            self.player).count(
            )  # Includes the current session if it's a victory

        L.Assert(
            self.play_number == expected_play_number,
            f"{pfx} Play number is wrong. Play number: {self.play_number}, Expected: {expected_play_number}."
        )
        L.Assert(
            self.victory_count == expected_victory_count,
            f"{pfx} Victory count is wrong. Victory count: {self.victory_count}, Expected: {expected_victory_count}."
        )

        return L.assertion_failures

    def clean(self):
        return  # Disable for now, enable only for testing

        # Find the previous performance for this player at this game and copy
        # the trueskill after values to the trueskill before values in this
        # performance or from initials if no previous.
        previous = self.previous_play

        if previous is None:
            TS = TrueskillSettings()

            self.trueskill_mu_before = TS.mu0
            self.trueskill_sigma_before = TS.sigma0
            self.trueskill_eta_before = 0
        else:
            self.trueskill_mu_before = previous.trueskill_mu_after
            self.trueskill_sigma_before = previous.trueskill_sigma_after
            self.trueskill_eta_before = previous.trueskill_eta_after

        # Catch the Trueskill settings in effect now.
        TS = TrueskillSettings()
        self.trueskill_mu0 = TS.mu0
        self.trueskill_sigma0 = TS.sigma0
        self.trueskill_beta = TS.beta
        self.trueskill_delta = TS.delta
        self.trueskill_tau = self.session.game.trueskill_tau
        self.trueskill_p = self.session.game.trueskill_p

        # If any of these settings have changed since the previous performance (for this player at this game)
        # Then we have an error that demands a rebuild of ratings for this game.
        if not previous is None:
            if self.trueskill_mu0 != previous.trueskill_mu0:
                raise ValidationError(
                    "Global Trueskill µ0 has changed (from {} to {}). Either reset the value of rebuild all ratings for game {} ({}) "
                    .format(self.trueskill_mu0, previous.trueskill_mu0,
                            self.session.game.pk, self.session.game))
            if self.trueskill_sigma0 != previous.trueskill_sigma0:
                raise ValidationError(
                    "Global Trueskill σ0 has changed (from {} to {}). Either reset the value of rebuild all ratings for game {} ({}) "
                    .format(self.trueskill_sigma0, previous.trueskill_sigma0,
                            self.session.game.pk, self.session.game))
            if self.trueskill_beta != previous.trueskill_beta:
                raise ValidationError(
                    "Global Trueskill ß has changed (from {} to {}). Either reset the value of rebuild all ratings for game {} ({}) "
                    .format(self.trueskill_beta, previous.trueskill_beta,
                            self.session.game.pk, self.session.game))
            if self.trueskill_delta != previous.trueskill_delta:
                raise ValidationError(
                    "Global Trueskill δ has changed (from {} to {}). Either reset the value of rebuild all ratings for game {} ({}) "
                    .format(self.trueskill_delta, previous.trueskill_delta,
                            self.session.game.pk, self.session.game))
            if self.trueskill_tau != previous.trueskill_tau:
                raise ValidationError(
                    "Game Trueskill τ has changed (from {} to {}). Either reset the value of rebuild all ratings for game {} ({}) "
                    .format(self.trueskill_tau, previous.trueskill_tau,
                            self.session.game.pk, self.session.game))
            if self.trueskill_p != previous.trueskill_p:
                raise ValidationError(
                    "Game Trueskill p has changed (from {} to {}). Either reset the value of rebuild all ratings for game {} ({}) "
                    .format(self.trueskill_p, previous.trueskill_p,
                            self.session.game.pk, self.session.game))

        # Update the play counters too. We know this form submisison means one more play but we don't necessariuly know if it's
        # a victury yet (as that is stored with an associated Rank which may or may not have been saved yet).
        self.play_number = previous.play_number + 1

        if self.session.rank(self.player):
            self.victory_count = previous.victory_count + 1 if self.session.rank(
                self.player).rank == 1 else previous.victory_count

        # Trueskill Impact is calculated at the session level not the individual performance level.
        # The trueskill after settings for the performance will be calculated there.
        pass

    add_related = None
    sort_by = ['session.date_time', 'rank.rank', 'player.name_nickname'
               ]  # Need player to sort ties and team members.

    # It is crucial that Performances for a session are ordered the same as Ranks when a rich form is constructed
    # Each row on a form in a standard session submission has a rank and a performance associated with it and the
    # player for each object must agree.
    @classmethod
    def form_order(cls, performances) -> QuerySet:
        '''
        Form field ordering support for Django Generic Form Extenions RelatedFormsets

        if this class method exists, DGFE will call it to order objects when building related forms.

        This returns the performances in order of the players ranking. Must return a QuerySet.

        :param performances:  A QuerySet
        '''
        # Annotate with ranking ('rank' is a method above and clashes, crashing the queryset evaluation)
        sfilter = Q(session=OuterRef('session'))
        ipfilter = Q(player=OuterRef('player'))
        tpfilter = Q(team__players=OuterRef('player'))
        rfilter = sfilter & (ipfilter | tpfilter)
        ranking = Subquery(Rank.objects.filter(rfilter).values('rank'))
        ranked_performances = performances.annotate(ranking=ranking)
        return ranked_performances.order_by('ranking')

    def __unicode__(self):
        return u'{}'.format(self.player)

    def __str__(self):
        return self.__unicode__()

    def __verbose_str__(self):
        if self.session is None:  # Don't crash of the performance is orphaned!
            when = "<no time>"
            game = "<no game>"
        else:
            when = self.session.date_time
            game = self.session.game
        performer = self.player
        return u'{} - {:%d, %b %Y} - {}'.format(game, when, performer)

    def __rich_str__(self, link=None):
        if self.session is None:  # Don't crash of the performance is orphaned!
            when = "<no time>"
            game = "<no game>"
        else:
            when = self.session.date_time
            game = field_render(self.session.game, link)
        performer = field_render(self.player, link)
        performance = "{:.0%} participation, play number {}, {:+.1f} teeth".format(
            self.partial_play_weighting, self.play_number,
            self.trueskill_eta_after - self.trueskill_eta_before)
        return u'{} - {:%d, %b %Y} - {}: {}'.format(
            game, when, performer,
            field_render(performance, link_target_url(self, link)))

    def __detail_str__(self, link=None):
        if self.session is None:  # Don't crash of the performance is orphaned!
            when = "<no time>"
            game = "<no game>"
            players = "<no players>"
        else:
            when = self.session.date_time
            game = field_render(self.session.game, link)
            players = len(self.session.players)

        performer = field_render(self.player, link)

        detail = u'{} - {:%a, %-d %b %Y} - {}:<UL>'.format(
            game, when, performer)
        detail += "<LI>Players: {}</LI>".format(players)
        detail += "<LI>Play number: {}</LI>".format(self.play_number)
        detail += "<LI>Play Weighting: {:.0%}</LI>".format(
            self.partial_play_weighting)
        detail += "<LI>Trueskill Delta: {:+.1f} teeth</LI>".format(
            self.trueskill_eta_after - self.trueskill_eta_before)
        detail += "<LI>Victories: {}</LI>".format(self.victory_count)
        detail += "</UL>"
        return detail

    class Meta(AdminModel.Meta):
        verbose_name = "Performance"
        verbose_name_plural = "Performances"
        ordering = ['session', 'player']