示例#1
0
class ScopeDefinition(models.Model):

    # the identifier used to specify this scope within a certificate
    id = models.CharField(primary_key=True, max_length=20)

    # the Morango profile with which this scope is associated
    profile = models.CharField(max_length=20)

    # version number is incremented whenever scope definition is updated
    version = models.IntegerField()

    # the scope_param key that the primary partition value will be inserted into when generating a root cert
    # (if this is not set, then this scope definition cannot be used to generate a root cert)
    primary_scope_param_key = models.CharField(max_length=20, blank=True)

    # human-readable description
    # (can include string template refs to scope params e.g. "Allows syncing data for user ${username}")
    description = models.TextField()

    # filter templates, in the form of a newline-delimited list of colon-delimited partition strings
    # (can include string template refs to scope params e.g. "122211:singleuser:${user_id}")
    read_filter_template = models.TextField()
    write_filter_template = models.TextField()
    read_write_filter_template = models.TextField()

    @classmethod
    def retrieve_by_id(cls, scope_def_id):
        try:
            return cls.objects.get(id=scope_def_id)
        except ScopeDefinition.DoesNotExist:
            call_command("loaddata", "scopedefinitions")
            return cls.objects.get(id=scope_def_id)

    def get_scope(self, params):
        return Scope(definition=self, params=params)
示例#2
0
class Country(CountryBase):
    iso = models.CharField("ISO", max_length=3, primary_key=True)
    iso_code = models.CharField("Iso alpha 2", max_length=2, default="")
    name = models.CharField("Name", max_length=128)

    class Meta:
        verbose_name = "Country"
        verbose_name_plural = "Countries"
        ordering = ("name",)
        db_table = "country"

    def __str__(self):
        return self.name
示例#3
0
class Country(CountryBase):
    iso = models.CharField('ISO', max_length=3, primary_key=True)
    iso_code = models.CharField('Iso alpha 2', max_length=2, default='')
    name = models.CharField('Name', max_length=128)

    class Meta:
        verbose_name = 'Country'
        verbose_name_plural = 'Countries'
        ordering = ('name', )
        db_table = "country"

    def __str__(self):
        return self.name
示例#4
0
class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)

    home_country = models.ForeignKey(
        Country, on_delete=models.CASCADE, related_name="home_country"
    )
    countries = models.ManyToManyField(Country)

    affiliation = models.CharField(
        "Institutional affiliation",
        max_length=255,
        null=True,
        blank=True,
    )

    position = models.CharField(
        "Position",
        max_length=255,
        null=True,
        blank=True,
    )

    approve_url = models.URLField("Approve URL", max_length=255, null=True, blank=True)

    other_role = models.CharField(
        'Other role',
        max_length=255,
        null=True,
        blank=True,
    )

    @property
    def roles(self):
        return get_user_roles(self.user)

    @property
    def flag(self):
        """Returns alpha3 from iso3."""
        return pycountry.countries.get(alpha_3=self.home_country.iso).alpha_3

    @property
    def country(self):
        if self.home_country is None:
            # @TODO  Please remove this when User management is added
            #      in the frontend.
            return "User without a country"
        return self.home_country.name

    def __str__(self):
        return self.user.username
示例#5
0
class TaxonomyTag(models.Model):
    # NOTE: The name must not contain the character ";".
    name = models.CharField(max_length=255)
    group = models.ForeignKey(TaxonomyTagGroup, related_name='tags')

    def __str__(self):
        return "Tag " + self.name
示例#6
0
class BidSuggestion(models.Model):
    bid = models.ForeignKey('Bid',
                            related_name='suggestions',
                            null=False,
                            on_delete=models.PROTECT)
    name = models.CharField(max_length=64,
                            blank=False,
                            null=False,
                            verbose_name='Name')

    class Meta:
        app_label = 'tracker'
        ordering = ['name']

    def __init__(self):
        raise Exception('Nothing should be using this any more')

    def clean(self):
        sameBid = BidSuggestion.objects.filter(
            Q(name__iexact=self.name)
            & (Q(bid__event=self.bid.get_event())
               | Q(bid__speedrun__event=self.bid.get_event())))
        if sameBid.exists():
            if sameBid.count() > 1 or sameBid[0].id != self.id:
                raise ValidationError(
                    'Cannot have a bid suggestion with the same name within the same event.'
                )

        # If set, limit the length of suggestions based on the parent bid's
        # setting

    def __str__(self):
        return self.name + ' -- ' + str(self.bid)
示例#7
0
class Nonce(UUIDModelMixin):
    """
    Stores temporary nonce values used for cryptographic handshakes during syncing.
    These nonces are requested by the client, and then generated and stored by the server.
    When the client then goes to initiate a sync session, it signs the nonce value using
    the private key from the certificate it is using for the session, to prove to the
    server that it owns the certificate. The server checks that the nonce exists and hasn't
    expired, and then deletes it.
    """

    uuid_input_fields = "RANDOM"

    timestamp = models.DateTimeField(default=timezone.now)
    ip = models.CharField(max_length=100, blank=True)

    @classmethod
    def use_nonce(cls, nonce_value):
        with transaction.atomic():
            # try fetching the nonce
            try:
                nonce = cls.objects.get(id=nonce_value)
            except cls.DoesNotExist:
                raise NonceDoesNotExist()
            # check that the nonce hasn't expired
            if not (0 <
                    (timezone.now() - nonce.timestamp).total_seconds() < 60):
                nonce.delete()
                raise NonceExpired()
            # now that we've used it, delete the nonce
            nonce.delete()
示例#8
0
class TaxonomyTagGroup(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return 'Tagging by ' + self.name

    class Meta:
        ordering = ['id']
示例#9
0
class Region(models.Model):
    name = models.CharField('Name', max_length=128)

    def __str__(self):
        return self.name

    def countries(self):
        return (object for object in self.country_set.all())
示例#10
0
class LegislationArticle(_TaxonomyModel):
    text = models.CharField(max_length=65535)
    legislation = models.ForeignKey(
        Legislation, on_delete=models.CASCADE, related_name="articles"
    )
    legislation_page = models.IntegerField(null=True, blank=True)
    code = models.CharField(max_length=256)  # aka Article number
    number = models.IntegerField(blank=True, null=True)  # populated from code
    identifier = models.IntegerField(blank=True, null=True, default=None)
    legispro_identifier = models.CharField(max_length=256, null=True, blank=True)
    # objects = LegislationSectionManager()

    class Meta(_TaxonomyModel.Meta):
        ordering = ["number", "code"]

    def __str__(self):
        return self.code

    def classifications_text(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.classifications.values_list("name", flat=True)
        )

    def tags_text(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.tags.values_list("name", flat=True)
        )

    def parent_tags(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.legislation.tags.values_list("name", flat=True)
        )

    def parent_classifications(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.legislation.classifications.values_list("name", flat=True)
        )

    def save(self, *args, **kwargs):
        match = re.search("\d+", self.code)
        if match:
            self.number = int(match.group(0))
        return super().save(*args, **kwargs)
示例#11
0
class LogicalCategory(models.Model):
    name = models.CharField(max_length=1024)
    code = models.IntegerField()

    class Meta:
        verbose_name = "Logical category"
        verbose_name_plural = "Logical categories"
        ordering = ["code"]

    def __str__(self):
        return self.name
示例#12
0
class BidSuggestion(models.Model):
    bid = models.ForeignKey('Bid', related_name='suggestions',
                            null=False, on_delete=models.PROTECT)
    name = models.CharField(max_length=64, blank=False,
                            null=False, verbose_name="Name")

    class Meta:
        app_label = 'tracker'
        ordering = ['name']

    def clean(self):
        sameBid = BidSuggestion.objects.filter(Q(name__iexact=self.name) & (
            Q(bid__event=self.bid.get_event()) | Q(bid__speedrun__event=self.bid.get_event())))
        if sameBid.exists():
            if sameBid.count() > 1 or sameBid[0].id != self.id:
                raise ValidationError(
                    "Cannot have a bid suggestion with the same name within the same event.")

    def __unicode__(self):
        return self.name + " -- " + unicode(self.bid)
示例#13
0
class Question(mptt.models.MPTTModel):

    text = models.CharField(max_length=1024)
    parent = mptt.models.TreeForeignKey('self',
                                        null=True,
                                        blank=True,
                                        related_name='children')
    parent_answer = models.NullBooleanField(default=None)
    order = models.IntegerField(blank=True)

    classification = models.ForeignKey(TaxonomyClassification,
                                       null=True,
                                       blank=True)

    class Meta:
        verbose_name = 'Question'
        verbose_name_plural = 'Questions'

    class MPTTMeta:
        order_insertion_by = ['order']

    def save(self, *args, **kwargs):
        if not self.order:
            self.order = utils.set_order(self.classification, self.parent)
        super(Question, self).save(*args, **kwargs)

    @property
    def full_order(self):
        return ".".join([
            str(question.order)
            for question in self.get_ancestors(include_self=True)
        ])

    def __str__(self):
        if self.parent:
            return "Question: %s with parent answer: %s" % (self.full_order,
                                                            self.parent_answer)
        else:
            return "C: %s Question: %s" % (self.classification.code,
                                           self.order)
示例#14
0
class PrioritySector(models.Model):
    name = models.CharField('Name', max_length=255)

    def __str__(self):
        return self.name
示例#15
0
class Certificate(mptt.models.MPTTModel, UUIDModelMixin):

    uuid_input_fields = ("public_key", "profile")

    parent = models.ForeignKey("Certificate", blank=True, null=True)

    # the Morango profile with which this certificate is associated
    profile = models.CharField(max_length=20)

    # scope of this certificate, and version of the scope, along with associated params
    scope_definition = models.ForeignKey("ScopeDefinition")
    scope_version = models.IntegerField()
    scope_params = models.TextField()  # JSON dict of values to insert into scope definitions

    # track the certificate's public key so we can verify any certificates it signs
    public_key = PublicKeyField()

    # the JSON-serialized copy of all the fields above
    serialized = models.TextField()

    # signature from the private key of the parent certificate, of the "serialized" field text
    signature = models.TextField()

    # when we own a certificate, we'll have the private key for it (otherwise not)
    _private_key = PrivateKeyField(blank=True, null=True, db_column="private_key")

    @property
    def private_key(self):
        return self._private_key

    @private_key.setter
    def private_key(self, value):
        self._private_key = value
        if value and not self.public_key:
            self.public_key = Key(public_key_string=self._private_key.get_public_key_string())

    @classmethod
    def generate_root_certificate(cls, scope_def_id, **extra_scope_params):

        # attempt to retrieve the requested scope definition object
        scope_def = ScopeDefinition.retrieve_by_id(scope_def_id)

        # create a certificate model instance
        cert = cls()

        # set the scope definition foreign key, and read some values off of the scope definition model
        cert.scope_definition = scope_def
        cert.scope_version = scope_def.version
        cert.profile = scope_def.profile
        primary_scope_param_key = scope_def.primary_scope_param_key
        assert primary_scope_param_key, "Root cert can only be created for ScopeDefinition that has primary_scope_param_key defined"

        # generate a key and extract the public key component
        cert.private_key = Key()
        cert.public_key = Key(public_key_string=cert.private_key.get_public_key_string())

        # calculate the certificate's ID on the basis of the profile and public key
        cert.id = cert.calculate_uuid()

        # set the scope params to include the primary partition value and any additional params
        scope_params = {primary_scope_param_key: cert.id}
        scope_params.update(extra_scope_params)
        cert.scope_params = json.dumps(scope_params)

        # self-sign the certificate
        cert.sign_certificate(cert)

        # save and return the certificate
        cert.save()
        return cert

    def serialize(self):
        if not self.id:
            self.id = self.calculate_uuid()
        data = {
            "id": self.id,
            "parent_id": self.parent_id,
            "profile": self.profile,
            "scope_definition_id": self.scope_definition_id,
            "scope_version": self.scope_version,
            "scope_params": self.scope_params,
            "public_key_string": self.public_key.get_public_key_string(),
        }
        return json.dumps(data)

    @classmethod
    def deserialize(cls, serialized, signature):
        data = json.loads(serialized)
        model = cls(
            id=data["id"],
            parent_id=data["parent_id"],
            profile=data["profile"],
            scope_definition_id=data["scope_definition_id"],
            scope_version=data["scope_version"],
            scope_params=data["scope_params"],
            public_key=Key(public_key_string=data["public_key_string"]),
            serialized=serialized,
            signature=signature,
        )
        return model

    def sign_certificate(self, cert_to_sign):
        if not cert_to_sign.serialized:
            cert_to_sign.serialized = cert_to_sign.serialize()
        cert_to_sign.signature = self.sign(cert_to_sign.serialized)

    def check_certificate(self):

        # check that the certificate's ID is properly calculated
        if self.id != self.calculate_uuid():
            raise CertificateIDInvalid("Certificate ID is {} but should be {}".format(self.id, self.calculate_uuid()))

        if not self.parent:  # self-signed root certificate
            # check that the certificate is properly self-signed
            if not self.verify(self.serialized, self.signature):
                raise CertificateSignatureInvalid()
            # check that the certificate scopes all start with the primary partition value
            scope = self.get_scope()
            for item in scope.read_filter + scope.write_filter:
                if not item.startswith(self.id):
                    raise CertificateRootScopeInvalid("Scope entry {} does not start with primary partition {}".format(item, self.id))
        else:  # non-root child certificate
            # check that the certificate is properly signed by its parent
            if not self.parent.verify(self.serialized, self.signature):
                raise CertificateSignatureInvalid()
            # check that certificate's scope is a subset of parent's scope
            self.get_scope().verify_subset_of(self.parent.get_scope())
            # check that certificate is for same profile as parent
            if self.profile != self.parent.profile:
                raise CertificateProfileInvalid("Certificate profile is {} but parent's is {}" \
                                                .format(self.profile, self.parent.profile))

    def sign(self, value):
        assert self.private_key, "Can only sign using certificates that have private keys"
        return self.private_key.sign(value)

    def verify(self, value, signature):
        return self.public_key.verify(value, signature)

    def get_scope(self):
        return self.scope_definition.get_scope(self.scope_params)
示例#16
0
class LegislationSection(_TaxonomyModel, mptt.models.MPTTModel):
    text = models.TextField()
    legislation = models.ForeignKey(
        Legislation, on_delete=models.CASCADE, related_name="sections"
    )
    legislation_page = models.IntegerField(null=True, blank=True)
    number = models.IntegerField(blank=True, null=True)  # populated from code
    identifier = models.IntegerField(blank=True, null=True, default=None)
    legispro_identifier = models.CharField(max_length=256, null=True, blank=True)
    code = models.CharField(max_length=256, blank=True)
    code_order = models.CharField(max_length=256, blank=True)
    parent = mptt.models.TreeForeignKey(
        "self", null=True, blank=True, on_delete=models.CASCADE, related_name="children"
    )
    objects = LegislationSectionManager()

    class Meta(_TaxonomyModel.Meta):
        ordering = ['code_order']

    class MPTTMeta:
        order_insertion_by = ["code_order"]

    def get_children(self):
        return (
            super()
            .get_children()
            .extra(
                select={
                    "code_order_fix": "string_to_array(code_order, '.')::int[]",
                },
            )
            .order_by("code_order_fix")
        )

    def __str__(self):
        return self.code

    def classifications_text(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.classifications.values_list("name", flat=True)
        )

    def tags_text(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.tags.values_list("name", flat=True)
        )

    def parent_tags(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.legislation.tags.values_list("name", flat=True)
        )

    def parent_classifications(self):
        return settings.TAXONOMY_CONNECTOR.join(
            self.legislation.classifications.values_list("name", flat=True)
        )

    def save(self, *args, **kwargs):
        match = re.search("\d+", self.code)
        if match:
            self.number = int(match.group(0))
        return super().save(*args, **kwargs)
示例#17
0
class Certificate(mptt.models.MPTTModel, UUIDModelMixin):

    uuid_input_fields = ("public_key", "profile", "salt")

    parent = models.ForeignKey("Certificate", blank=True, null=True)

    # the Morango profile with which this certificate is associated
    profile = models.CharField(max_length=20)

    # scope of this certificate, and version of the scope, along with associated params
    scope_definition = models.ForeignKey("ScopeDefinition")
    scope_version = models.IntegerField()
    scope_params = models.TextField(
    )  # JSON dict of values to insert into scope definitions

    # track the certificate's public key so we can verify any certificates it signs
    public_key = PublicKeyField()

    # a salt value to include in the UUID calculation, to prevent CSR requests from forcing ID collisions
    salt = models.CharField(max_length=32, blank=True)

    # the JSON-serialized copy of all the fields above
    serialized = models.TextField()

    # signature from the private key of the parent certificate, of the "serialized" field text
    signature = models.TextField()

    # when we own a certificate, we'll have the private key for it (otherwise not)
    _private_key = PrivateKeyField(blank=True,
                                   null=True,
                                   db_column="private_key")

    @property
    def private_key(self):
        return self._private_key

    @private_key.setter
    def private_key(self, value):
        self._private_key = value
        if value and not self.public_key:
            self.public_key = Key(
                public_key_string=self._private_key.get_public_key_string())

    @classmethod
    def generate_root_certificate(cls, scope_def_id, **extra_scope_params):

        # attempt to retrieve the requested scope definition object
        scope_def = ScopeDefinition.retrieve_by_id(scope_def_id)

        # create a certificate model instance
        cert = cls()

        # set the scope definition foreign key, and read some values off of the scope definition model
        cert.scope_definition = scope_def
        cert.scope_version = scope_def.version
        cert.profile = scope_def.profile
        primary_scope_param_key = scope_def.primary_scope_param_key
        assert primary_scope_param_key, "Root cert can only be created for ScopeDefinition that has primary_scope_param_key defined"

        # generate a key and extract the public key component
        cert.private_key = Key()
        cert.public_key = Key(
            public_key_string=cert.private_key.get_public_key_string())

        # calculate the certificate's ID on the basis of the profile and public key
        cert.id = cert.calculate_uuid()

        # set the scope params to include the primary partition value and any additional params
        scope_params = {primary_scope_param_key: cert.id}
        scope_params.update(extra_scope_params)
        cert.scope_params = json.dumps(scope_params)

        # self-sign the certificate
        cert.sign_certificate(cert)

        # save and return the certificate
        cert.save()
        return cert

    def has_private_key(self):
        return self._private_key is not None

    def serialize(self):
        if not self.id:
            self.id = self.calculate_uuid()
        data = {
            "id": self.id,
            "parent_id": self.parent_id,
            "profile": self.profile,
            "salt": self.salt,
            "scope_definition_id": self.scope_definition_id,
            "scope_version": self.scope_version,
            "scope_params": self.scope_params,
            "public_key_string": self.public_key.get_public_key_string(),
        }
        return json.dumps(data)

    @classmethod
    def deserialize(cls, serialized, signature):
        data = json.loads(serialized)
        model = cls(
            id=data["id"],
            parent_id=data["parent_id"],
            profile=data["profile"],
            salt=data.get('salt') or '',
            scope_definition_id=data["scope_definition_id"],
            scope_version=data["scope_version"],
            scope_params=data["scope_params"],
            public_key=Key(public_key_string=data["public_key_string"]),
            serialized=serialized,
            signature=signature,
        )
        return model

    def _serialize_if_needed(self):
        if not self.serialized:
            self.serialized = self.serialize()

    def sign_certificate(self, cert_to_sign):
        cert_to_sign._serialize_if_needed()
        cert_to_sign.signature = self.sign(cert_to_sign.serialized)

    def check_certificate(self):

        # check that the certificate's ID is properly calculated
        if self.id != self.calculate_uuid():
            raise CertificateIDInvalid(
                "Certificate ID is {} but should be {}".format(
                    self.id, self.calculate_uuid()))

        if not self.parent:  # self-signed root certificate
            # check that the certificate is properly self-signed
            if not self.verify(self.serialized, self.signature):
                raise CertificateSignatureInvalid()
            # check that the certificate scopes all start with the primary partition value
            scope = self.get_scope()
            for item in scope.read_filter + scope.write_filter:
                if not item.startswith(self.id):
                    raise CertificateRootScopeInvalid(
                        "Scope entry {} does not start with primary partition {}"
                        .format(item, self.id))
        else:  # non-root child certificate
            # check that the certificate is properly signed by its parent
            if not self.parent.verify(self.serialized, self.signature):
                raise CertificateSignatureInvalid()
            # check that certificate's scope is a subset of parent's scope
            if not self.get_scope().is_subset_of(self.parent.get_scope()):
                raise CertificateScopeNotSubset()
            # check that certificate is for same profile as parent
            if self.profile != self.parent.profile:
                raise CertificateProfileInvalid("Certificate profile is {} but parent's is {}" \
                                                .format(self.profile, self.parent.profile))

    @classmethod
    def save_certificate_chain(cls, cert_chain, expected_last_id=None):

        # parse the chain from json if needed
        if isinstance(cert_chain, string_types):
            cert_chain = json.loads(cert_chain)

        # start from the bottom of the chain
        cert_data = cert_chain[-1]

        # create an in-memory instance of the cert from the serialized data and signature
        cert = cls.deserialize(cert_data["serialized"], cert_data["signature"])

        # verify the id of the cert matches the id of the outer serialized data
        assert cert_data["id"] == cert.id

        # check that the expected ID matches, if specified
        if expected_last_id:
            assert cert.id == expected_last_id

        # if cert already exists locally, it's already been verified, so no need to continue
        # (this also means we have the full cert chain for it, given the `parent` relations)
        try:
            return cls.objects.get(id=cert.id)
        except cls.DoesNotExist:
            pass

        # recurse up the certificate chain, until we hit a cert that exists or is the root
        if len(cert_chain) > 1:
            cls.save_certificate_chain(cert_chain[:-1],
                                       expected_last_id=cert.parent_id)
        else:
            assert not cert.parent_id, "First cert in chain must be a root cert (no parent)"

        # ensure the certificate checks out (now that we know its parent, if any, is saved)
        cert.check_certificate()

        # save the certificate, as it's now fully verified
        cert.save()

        return cert

    def sign(self, value):
        assert self.private_key, "Can only sign using certificates that have private keys"
        return self.private_key.sign(value)

    def verify(self, value, signature):
        return self.public_key.verify(value, signature)

    def get_scope(self):
        return self.scope_definition.get_scope(self.scope_params)

    def __str__(self):
        if self.scope_definition:
            return self.scope_definition.get_description(self.scope_params)
示例#18
0
class TaxonomyClassification(mptt.models.MPTTModel):
    # NOTE: The name must not contain the character ";".
    name = models.CharField(max_length=255)
    code = models.CharField(max_length=16, unique=True, blank=True)
    legispro_code = models.CharField(max_length=16, blank=True)
    details = models.TextField(null=True, default='')
    parent = mptt.models.TreeForeignKey('self',
                                        null=True,
                                        blank=True,
                                        related_name='children')

    class Meta:
        verbose_name = 'Taxonomy Classification'
        verbose_name_plural = 'Taxonomy Classifications'
        ordering = ('code', )

    class MPTTMeta:
        order_insertion_by = ['code']

    @classmethod
    def _pre_save_classification_code_on_create(cls, instance):
        """Logic executed before saving a new TaxonomyClassification instance.
        Set the next code for the classification.
        """
        instance.code = utils.generate_code(cls, instance)

    @staticmethod
    def _pre_save_classification_code_on_edit(instance):
        """Logic executed before editing an TaxonomyClassification instance.

        Update the code for every child to match the parent classification.
        """
        for classification in instance.children.all():
            parts = classification.code.split('.')
            suffix_code = parts[-1]
            classification.code = '{0}.{1}'.format(instance.code, suffix_code)
            classification.save()

    @staticmethod
    def pre_save_classification_code(**kwargs):

        instance = kwargs['instance']

        if instance.code:
            TaxonomyClassification._pre_save_classification_code_on_edit(
                instance)
        else:
            TaxonomyClassification._pre_save_classification_code_on_create(
                instance)

    def get_classification_level(self):
        # The logical classification of taxonomy starts from 1
        # The tree level of an object starts from 0
        return self.get_level() + 1

    def get_children(self):
        return super().get_children().extra(select={
            'code_fix':
            "string_to_array(code, '.')::int[]",
        }, ).order_by('code_fix')

    def __str__(self):
        return "{} classification: {}".format(self.code, self.name)
示例#19
0
class Bid(mptt.models.MPTTModel):
    objects = BidManager()
    event = models.ForeignKey(
        'Event',
        on_delete=models.PROTECT,
        verbose_name='Event',
        null=True,
        blank=True,
        related_name='bids',
        help_text='Required for top level bids if Run is not set',
    )
    speedrun = models.ForeignKey(
        'SpeedRun',
        on_delete=models.PROTECT,
        verbose_name='Run',
        null=True,
        blank=True,
        related_name='bids',
    )
    parent = mptt.models.TreeForeignKey(
        'self',
        on_delete=models.PROTECT,
        verbose_name='Parent',
        editable=False,
        null=True,
        blank=True,
        related_name='options',
    )
    name = models.CharField(max_length=64)
    state = models.CharField(
        max_length=32,
        db_index=True,
        default='OPENED',
        choices=(
            ('PENDING', 'Pending'),
            ('DENIED', 'Denied'),
            ('HIDDEN', 'Hidden'),
            ('OPENED', 'Opened'),
            ('CLOSED', 'Closed'),
        ),
    )
    description = models.TextField(max_length=1024, blank=True)
    shortdescription = models.TextField(
        max_length=256,
        blank=True,
        verbose_name='Short Description',
        help_text='Alternative description text to display in tight spaces',
    )
    goal = models.DecimalField(decimal_places=2,
                               max_digits=20,
                               null=True,
                               blank=True,
                               default=None)
    istarget = models.BooleanField(
        default=False,
        verbose_name='Target',
        help_text=
        "Set this if this bid is a 'target' for donations (bottom level choice or challenge)",
    )
    allowuseroptions = models.BooleanField(
        default=False,
        verbose_name='Allow User Options',
        help_text=
        'If set, this will allow donors to specify their own options on the donate page (pending moderator approval)',
    )
    option_max_length = models.PositiveSmallIntegerField(
        'Max length of user suggestions',
        blank=True,
        null=True,
        default=None,
        validators=[MinValueValidator(1),
                    MaxValueValidator(64)],
        help_text=
        'If allowuseroptions is set, this sets the maximum length of user-submitted bid suggestions',
    )
    revealedtime = models.DateTimeField(verbose_name='Revealed Time',
                                        null=True,
                                        blank=True)
    biddependency = models.ForeignKey(
        'self',
        on_delete=models.PROTECT,
        verbose_name='Dependency',
        null=True,
        blank=True,
        related_name='dependent_bids',
    )
    total = models.DecimalField(decimal_places=2,
                                max_digits=20,
                                editable=False,
                                default=Decimal('0.00'))
    count = models.IntegerField(editable=False)

    class Meta:
        app_label = 'tracker'
        unique_together = ((
            'event',
            'name',
            'speedrun',
            'parent',
        ), )
        ordering = [
            'event__datetime', 'speedrun__starttime', 'parent__name', 'name'
        ]
        permissions = (
            ('top_level_bid', 'Can create new top level bids'),
            ('delete_all_bids', 'Can delete bids with donations attached'),
            ('view_hidden_bid', 'Can view hidden bids'),
        )

    class MPTTMeta:
        order_insertion_by = ['name']

    def get_absolute_url(self):
        return reverse('tracker:bid', args=(self.id, ))

    def natural_key(self):
        if self.parent:
            return (
                self.event.natural_key(),
                self.name,
                self.speedrun.natural_key() if self.speedrun else None,
                self.parent.natural_key(),
            )
        elif self.speedrun:
            return (self.event.natural_key(), self.name,
                    self.speedrun.natural_key())
        else:
            return (self.event.natural_key(), self.name)

    def clean(self):
        # Manually de-normalize speedrun/event/state to help with searching
        # TODO: refactor this logic, it should be correct, but is probably not minimal

        if self.option_max_length:
            if not self.allowuseroptions:
                raise ValidationError(
                    _('Cannot set option_max_length without allowuseroptions'),
                    code='invalid',
                )
                # FIXME: why is this printing 'please enter a whole number'?
                # raise ValidationError(
                #     {
                #         'option_max_length': ValidationError(
                #             _('Cannot set option_max_length without allowuseroptions'),
                #             code='invalid',
                #         ),
                #     }
                # )
            if self.pk:
                for child in self.get_children():
                    if len(child.name) > self.option_max_length:
                        raise ValidationError(
                            _('Cannot set option_max_length to %(length)d, child name `%(name)s` is too long'
                              ),
                            code='invalid',
                            params={
                                'length': self.option_max_length,
                                'name': child.name,
                            },
                        )
                        # TODO: why is this printing 'please enter a whole number'?
                        # raise ValidationError({
                        #     'option_max_length': ValidationError(
                        #         _('Cannot set option_max_length to %(length), child name %(name) is too long'),
                        #         code='invalid',
                        #         params={
                        #             'length': self.option_max_length,
                        #             'name': child.name,
                        #         }
                        #     ),
                        # })

        if self.parent:
            max_len = self.parent.option_max_length
            if max_len and len(self.name) > max_len:
                raise ValidationError({
                    'name':
                    ValidationError(
                        _('Name is longer than %(limit)s characters'),
                        params={'limit': max_len},
                        code='invalid',
                    ),
                })
        if self.biddependency:
            if self.parent or self.speedrun:
                if self.event != self.biddependency.event:
                    raise ValidationError(
                        'Dependent bids must be on the same event')
        if not self.parent:
            if not self.get_event():
                raise ValidationError(
                    'Top level bids must have their event set')
        if not self.goal:
            self.goal = None
        elif self.goal <= Decimal('0.0'):
            raise ValidationError('Goal should be a positive value')
        if self.state in ['PENDING', 'DENIED'
                          ] and (not self.istarget or not self.parent
                                 or not self.parent.allowuseroptions):
            raise ValidationError({
                'state':
                f'State `{self.state}` can only be set on targets with parents that allow user options'
            })
        if self.istarget and self.options.count() != 0:
            raise ValidationError('Targets cannot have children')
        if self.parent and self.parent.istarget:
            raise ValidationError('Cannot set that parent, parent is a target')
        if self.istarget and self.allowuseroptions:
            raise ValidationError(
                'A bid target cannot allow user options, since it cannot have children.'
            )
        if (not self.allowuseroptions and self.pk and
                self.get_children().filter(state__in=['PENDING', 'DENIED'])):
            raise ValidationError({
                'allowuseroptions':
                'Bid has pending/denied children, cannot remove allowing user options'
            })
        same_name = Bid.objects.filter(
            speedrun=self.speedrun,
            event=self.event,
            parent=self.parent,
            name__iexact=self.name,
        ).exclude(pk=self.pk)
        if same_name.exists():
            raise ValidationError(
                'Cannot have a bid under the same event/run/parent with the same name'
            )

    def save(self, *args, skip_parent=False, **kwargs):
        if self.parent:
            self.check_parent()
        if self.speedrun:
            self.event = self.speedrun.event
        if self.state in ['OPENED', 'CLOSED'] and not self.revealedtime:
            self.revealedtime = datetime.utcnow().replace(tzinfo=pytz.utc)
        if self.biddependency:
            self.event = self.biddependency.event
            if not self.speedrun:
                self.speedrun = self.biddependency.speedrun
        self.update_total()
        super(Bid, self).save(*args, **kwargs)
        if self.pk:
            for option in self.get_descendants():
                if option.check_parent():
                    option.save(skip_parent=True)
        if self.parent and not skip_parent:
            self.parent.save()

    def check_parent(self):
        changed = False
        if self.speedrun != self.parent.speedrun:
            self.speedrun = self.parent.speedrun
            changed = True
        if self.event != self.parent.event:
            self.event = self.parent.event
            changed = True
        if self.state not in ['PENDING', 'DENIED'
                              ] and self.state != self.parent.state:
            self.state = self.parent.state
            changed = True
        return changed

    @property
    def has_options(self):
        return self.allowuseroptions or self.public_options.exists()

    @property
    def public_options(self):
        return self.options.filter(Q(state='OPENED')
                                   | Q(state='CLOSED')).order_by('-total')

    def update_total(self):
        if self.istarget:
            self.total = self.bids.filter(
                donation__transactionstate='COMPLETED').aggregate(
                    Sum('amount'))['amount__sum'] or Decimal('0.00')
            self.count = self.bids.filter(
                donation__transactionstate='COMPLETED').count()
            # auto close this if it's a challenge with no children and the goal's been met
            if (self.goal and self.state == 'OPENED'
                    and self.total >= self.goal and self.istarget):
                self.state = 'CLOSED'
        else:
            options = self.options.exclude(state__in=('DENIED',
                                                      'PENDING')).aggregate(
                                                          Sum('total'),
                                                          Sum('count'))
            self.total = options['total__sum'] or Decimal('0.00')
            self.count = options['count__sum'] or 0

    def get_event(self):
        if self.speedrun:
            return self.speedrun.event
        else:
            return self.event

    def full_label(self, addMoney=True):
        result = [self.fullname()]
        if self.speedrun:
            result = [self.speedrun.name_with_category(), ' : '] + result
        if addMoney:
            result += [' $', '%0.2f' % self.total]
            if self.goal:
                result += [' / ', '%0.2f' % self.goal]
        return ''.join(result)

    def __str__(self):
        if self.parent:
            return f'{self.parent} (Parent) -- {self.name}'
        elif self.speedrun:
            return f'{self.speedrun.name_with_category()} (Run) -- {self.name}'
        else:
            return f'{self.event} (Event) -- {self.name}'

    def fullname(self):
        parent = self.parent.fullname() + ' -- ' if self.parent else ''
        return parent + self.name
示例#20
0
class SubRegion(models.Model):
    name = models.CharField('Name', max_length=128)

    def __str__(self):
        return self.name
示例#21
0
class LegalSystem(models.Model):
    name = models.CharField('Name', max_length=128)

    def __str__(self):
        return self.name
示例#22
0
class Legislation(_TaxonomyModel):
    title = models.CharField(max_length=256)
    abstract = models.CharField(max_length=1024, blank=True, null=True)
    country = models.ForeignKey(Country, related_name="legislations")
    language = models.CharField(choices=constants.ALL_LANGUAGES,
                                default=constants.DEFAULT_LANGUAGE_VALUE,
                                max_length=64)
    law_type = models.CharField(choices=constants.LEGISLATION_TYPE,
                                default=constants.LEGISLATION_DEFAULT_VALUE,
                                max_length=64)
    year = models.IntegerField(default=constants.LEGISLATION_YEAR_RANGE[-1])
    year_amendment = models.IntegerField(
        default=constants.LEGISLATION_DEFAULT_YEAR, blank=True, null=True)
    year_mention = models.CharField(max_length=1024, blank=True, null=True)
    geo_coverage = models.CharField(
        choices=constants.GEOGRAPHICAL_COVERAGE,
        default=constants.GEOGRAPHICAL_COVERAGE_DEFAULT_VALUE,
        max_length=64,
        null=True)
    source = models.CharField(max_length=256, blank=True, null=True)
    source_type = models.CharField(choices=constants.SOURCE_TYPE,
                                   default=constants.SOURCE_TYPE_DEFAULT_VALUE,
                                   max_length=64,
                                   blank=True,
                                   null=True)
    website = models.URLField(max_length=2000, blank=True, null=True)
    legispro_article = models.CharField(max_length=512, blank=True, null=True)
    import_from_legispro = models.BooleanField(default=False)
    date_created = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)
    pdf_file = models.FileField(null=True, blank=True)
    pdf_file_name = models.CharField(null=True, max_length=256)

    objects = LegislationManager()

    @property
    def country_name(self):
        return self.country.name

    @property
    def country_iso(self):
        return self.country.iso

    @property
    def other_legislations(self):
        other = {}
        for classification in self.classifications.all():
            other[classification] = Legislation.objects.filter(
                classifications__id__exact=classification.pk).exclude(
                    pk=self.pk).all()[:3]
        return other

    # @TODO: Change the __str__ to something more appropriate
    def __str__(self):
        return "Legislation: " + ' | '.join([self.country.name, self.law_type])

    def highlighted_title(self):
        """
        If this law was returned as a result of an elasticsearch query, return
        the title with the search terms highlighted. If not, return the original
        title.
        """
        return getattr(self, '_highlighted_title', self.title)

    def highlighted_abstract(self):
        """
        If this law was returned as a result of an elasticsearch query, return
        the abstract with the search terms highlighted. If not, return an empty
        string.
        """
        return getattr(self, '_highlighted_abstract', '')

    def highlighted_pdf_text(self):
        """
        If this law was returned as a result of an elasticsearch query, return
        the pdf_text with the search terms highlighted. If not, return an empty
        string.
        """
        return getattr(self, '_highlighted_pdf_text', '')

    def highlighted_classifications(self):
        """
        If this law was returned as a result of an elasticsearch query, return
        a list of classification names with the search terms highlighted. If
        not, return the original list of classification names.
        """
        return getattr(
            self, '_highlighted_classifications',
            self.classifications.all().values_list('name', flat=True))

    def highlighted_tags(self):
        """
        If this law was returned as a result of an elasticsearch query, return
        a list of tag names with the search terms highlighted. If not, return
        the original list of tag names.
        """
        return getattr(self, '_highlighted_tags',
                       self.tags.all().values_list('name', flat=True))

    def highlighted_articles(self):
        """
        If this law was returned as a result of an elasticsearch query, return
        a list of dictionaries representing articles with the search terms
        highlighted in the text field. If not, return an empty list.
        """
        return getattr(self, '_highlighted_articles', [])

    def save_pdf_pages(self):
        if settings.DEBUG:
            time_to_load_pdf = time.time()
        if settings.DEBUG:
            print("INFO: FS pdf file load time: %fs" %
                  (time.time() - time_to_load_pdf))
            time_begin_transaction = time.time()

        with transaction.atomic():
            pdf = pdftotext.PDF(self.pdf_file)
            for idx, page in enumerate(pdf):
                page = page.replace('\x00', '')
                LegislationPage(page_text="<pre>%s</pre>" % page,
                                page_number=idx + 1,
                                legislation=self).save()

        if settings.DEBUG:
            print("INFO: ORM models.LegislationPages save time: %fs" %
                  (time.time() - time_begin_transaction))

        # This is necessary in order to trigger the signal that will update the
        # ElasticSearch index.
        self.save()
示例#23
0
class FocusArea(models.Model):
    name = models.CharField('Name', max_length=255)

    def __str__(self):
        return self.name
示例#24
0
class Bid(mptt.models.MPTTModel):
    objects = BidManager()
    event = models.ForeignKey('Event', on_delete=models.PROTECT, verbose_name='Event', null=True,
                              blank=True, related_name='bids', help_text='Required for top level bids if Run is not set')
    speedrun = models.ForeignKey('SpeedRun', on_delete=models.PROTECT,
                                 verbose_name='Run', null=True, blank=True, related_name='bids')
    parent = mptt.models.TreeForeignKey('self', on_delete=models.PROTECT, verbose_name='Parent',
                                        editable=False, null=True, blank=True, related_name='options')
    name = models.CharField(max_length=64)
    state = models.CharField(max_length=32, choices=(('PENDING', 'Pending'), ('DENIED', 'Denied'), (
        'HIDDEN', 'Hidden'), ('OPENED', 'Opened'), ('CLOSED', 'Closed')), default='OPENED')
    description = models.TextField(max_length=1024, blank=True)
    shortdescription = models.TextField(max_length=256, blank=True, verbose_name='Short Description',
                                        help_text="Alternative description text to display in tight spaces")
    goal = models.DecimalField(
        decimal_places=2, max_digits=20, null=True, blank=True, default=None)
    istarget = models.BooleanField(default=False, verbose_name='Target',
                                   help_text="Set this if this bid is a 'target' for donations (bottom level choice or challenge)")
    allowuseroptions = models.BooleanField(default=False, verbose_name="Allow User Options",
                                           help_text="If set, this will allow donors to specify their own options on the donate page (pending moderator approval)")
    revealedtime = models.DateTimeField(
        verbose_name='Revealed Time', null=True, blank=True)
    biddependency = models.ForeignKey('self', on_delete=models.PROTECT,
                                      verbose_name='Dependency', null=True, blank=True, related_name='dependent_bids')
    total = models.DecimalField(
        decimal_places=2, max_digits=20, editable=False, default=Decimal('0.00'))
    count = models.IntegerField(editable=False)

    class Meta:
        app_label = 'tracker'
        unique_together = (('event', 'name', 'speedrun', 'parent',),)
        ordering = ['event__datetime',
                    'speedrun__starttime', 'parent__name', 'name']
        permissions = (
            ('top_level_bid', 'Can create new top level bids'),
            ('delete_all_bids', 'Can delete bids with donations attached'),
            ('view_hidden', 'Can view hidden bids'),
        )

    class MPTTMeta:
        order_insertion_by = ['name']

    def natural_key(self):
        if self.parent:
            return (self.event.natural_key(), self.name, self.speedrun.natural_key() if self.speedrun else None, self.parent.natural_key())
        elif self.speedrun:
            return (self.event.natural_key(), self.name, self.speedrun.natural_key())
        else:
            return (self.event.natural_key(), self.name)

    def clean(self):
        # Manually de-normalize speedrun/event/state to help with searching
        # TODO: refactor this logic, it should be correct, but is probably not minimal
        if self.speedrun:
            self.event = self.speedrun.event
        if self.parent:
            curr = self.parent
            while curr.parent != None:
                curr = curr.parent
            root = curr
            self.speedrun = root.speedrun
            self.event = root.event
            if self.state != 'PENDING' and self.state != 'DENIED':
                self.state = root.state
        if self.biddependency:
            if self.parent or self.speedrun:
                if self.event != self.biddependency.event:
                    raise ValidationError(
                        'Dependent bids must be on the same event')
            self.event = self.biddependency.event
            if not self.speedrun:
                self.speedrun = self.biddependency.speedrun
        if not self.parent:
            if not self.get_event():
                raise ValidationError(
                    'Top level bids must have their event set')
        if self.id:
            for option in self.get_descendants():
                option.speedrun = self.speedrun
                option.event = self.event
                if option.state != 'PENDING' and option.state != 'DENIED':
                    option.state = self.state
                option.save()
        if not self.goal:
            self.goal = None
        elif self.goal <= Decimal('0.0'):
            raise ValidationError('Goal should be a positive value')
        if self.istarget and self.options.count() != 0:
            raise ValidationError('Targets cannot have children')
        if self.parent and self.parent.istarget:
            raise ValidationError('Cannot set that parent, parent is a target')
        if self.istarget and self.allowuseroptions:
            raise ValidationError(
                'A bid target cannot allow user options, since it cannot have children.')
        sameName = Bid.objects.filter(
            speedrun=self.speedrun, event=self.event, parent=self.parent, name__iexact=self.name)
        if sameName.exists():
            if sameName.count() > 1 or sameName[0].id != self.id:
                raise ValidationError(
                    'Cannot have a bid under the same event/run/parent with the same name')
        if self.id == None or (sameName.exists() and sameName[0].state == 'HIDDEN' and self.state == 'OPENED'):
            self.revealedtime = datetime.utcnow().replace(tzinfo=pytz.utc)
        self.update_total()

    @property
    def has_options(self):
        return self.allowuseroptions or self.public_options.exists()

    @property
    def public_options(self):
        return self.options.filter(Q(state='OPENED') | Q(state='CLOSED')).order_by('-total')

    def update_total(self):
        if self.istarget:
            self.total = self.bids.filter(donation__transactionstate='COMPLETED').aggregate(
                Sum('amount'))['amount__sum'] or Decimal('0.00')
            self.count = self.bids.filter(
                donation__transactionstate='COMPLETED').count()
            # auto close this if it's a challenge with no children and the goal's been met
            if self.goal and self.state == 'OPENED' and self.total >= self.goal and self.istarget:
                self.state = 'CLOSED'
        else:
            options = self.options.exclude(state__in=(
                'HIDDEN', 'DENIED', 'PENDING')).aggregate(Sum('total'), Sum('count'))
            self.total = options['total__sum'] or Decimal('0.00')
            self.count = options['count__sum'] or 0

    def get_event(self):
        if self.speedrun:
            return self.speedrun.event
        else:
            return self.event

    def full_label(self, addMoney=True):
        result = [self.fullname()]
        if self.speedrun:
            result = [self.speedrun.name_with_category(), ' : '] + result
        if addMoney:
            result += [' $', '%0.2f' % self.total]
            if self.goal:
                result += [' / ', '%0.2f' % self.goal]
        return ''.join(result)

    def __unicode__(self):
        if self.parent:
            return unicode(self.parent) + ' -- ' + self.name
        elif self.speedrun:
            return self.speedrun.name_with_category() + ' -- ' + self.name
        else:
            return unicode(self.event) + ' -- ' + self.name

    def fullname(self):
        return ((self.parent.fullname() + ' -- ') if self.parent else '') + self.name