Example #1
0
    def ready(self):
        from django.contrib.auth import get_user_model
        User = get_user_model()

        import compat
        compat.User = User

        from relationships import models as rmodels
        rmodels.User = User

        from django.db.models import ManyToManyField
        from relationships.models import RelationshipsDescriptor, Relationship
        field = ManyToManyField(User, through=Relationship,
                    symmetrical=False, related_name='related_to')
        field.contribute_to_class(User, 'relationships')
        setattr(User, 'relationships', RelationshipsDescriptor())
Example #2
0
class QuestionSet(Model):
    def __str__(self):
        return u"{}".format(self.name)

    def get_absolute_url(self):
        return reverse('questionset_detail', kwargs={'pk': str(self.id)})

    slug = SlugField(unique=True, verbose_name=_("Slug"))
    name = CharField(max_length=255, verbose_name=_("Name"))
    questions = ManyToManyField('Question',
                                blank=True,
                                verbose_name=_("Questions"))
    resource_caches = ManyToManyField('ResourceCache', blank=True)

    def question_mapping(self, random_seed):
        q = self.questions.order_by('identifier').values_list('identifier')
        d = dict()
        r = random.Random(random_seed)
        c = r.sample(range(2**24), len(q))
        for n, i in enumerate(q):
            d[i[0]] = c[n]
        return d

    def ordered_question_ids(self):
        cache_id = 'questionset_question_ids_' + str(self.id)
        q_ids = cache.get(cache_id, None)
        if q_ids is None:
            q_ids = self.questions.order_by('id').values_list('id', flat=True)
            cache.set(cache_id, q_ids)
        return q_ids

    def cache_dir(self):
        return str(self.id) + "-" + self.slug

    def reverse_question_mapping(self, random_seed):
        return {v: k for k, v in self.question_mapping(random_seed).items()}

    def rebuild_caches(self, embed_images=True):
        html_resources = {}
        self.resource_caches.all().delete()
        html_cache = ResourceCache(format='zip')
        html_cache.file.name = os.path.join("caches", self.cache_dir(),
                                            "html_cache.zip")
        html_cache.save()
        ensure_dir_exists(html_cache.file.path)
        embeded_resource_ids = []
        html_resource_zip = zipfile.ZipFile(html_cache.file.path, 'w')
        for q in self.questions.all():
            for r in q.resource_set.filter(resource_type="html",
                                           part_of_solution=False):
                html_resources[q.identifier + '/' + r.relative_url] = r
                html_resource_zip.writestr(
                    q.identifier + '/' + 'Manifest.json',
                    json.dumps(q.manifest(safe=True)))
        for url, r in html_resources.items():
            if embed_images:
                index_soup = BeautifulSoup(r.as_bytes(), "lxml")
                imgs = index_soup.find_all('img')
                objs = index_soup.find_all('object')
                scripts = index_soup.find_all('script')
                for items, item_type, url_property in [(imgs, 'image', 'src'),
                                                       (objs, 'image', 'data'),
                                                       (scripts, 'javascript',
                                                        'src')]:
                    for i in items:
                        url_str = i.get(url_property, None)
                        if url_str is not None:
                            try:
                                data_res = r.question.resource_set.get(
                                    relative_url=url_str)
                                i[url_property] = (
                                    "data:" + data_res.mimetype + ";base64," +
                                    data_res.as_base64().decode('utf-8'))
                                embeded_resource_ids.append(data_res.id)
                            except Exception as e:
                                # TODO: handle exception, not print it!
                                print("error embedding", url, url_str, e)
                embeded_resource_ids.append(r.id)
                index_str = bytes(index_soup.prettify().encode('utf-8'))
            else:
                index_str = r.as_bytes()
            html_resource_zip.writestr(url, index_str)
            html_cache.resources.add(r)
        html_resource_zip.close()
        self.resource_caches.add(html_cache)
        for q in self.questions.all():
            for r in q.resource_set.exclude(part_of_solution=True).exclude(
                    id__in=embeded_resource_ids):
                print("must create cache for ", r.id, r.question.identifier,
                      r.file.name)
Example #3
0
class Profile(Model):
    def __str__(self):
        return str(self.user)

    def get_absolute_url(self):
        return reverse('profile_detail', kwargs={'pk': str(self.pk)})

    user = OneToOneField(User, on_delete=CASCADE)
    feature_level = IntegerField(choices=FEATURE_LEVELS, default=1)
    date_of_birth = DateField(null=True,
                              blank=True,
                              verbose_name=_('Date of birth'))
    gender = CharField(choices=GENDERS, max_length=16, blank=True)
    managed_profiles = ManyToManyField('Profile',
                                       related_name='managers',
                                       blank=True)
    created_codes = ManyToManyField(Code,
                                    blank=True,
                                    related_name='creator_set')
    received_codes = ManyToManyField(Code,
                                     blank=True,
                                     related_name='recipient_set')
    used_codes = ManyToManyField(Code, blank=True, related_name='user_set')
    question_sets = ManyToManyField(QuestionSet, blank=True)
    created_question_sets = ManyToManyField(QuestionSet,
                                            blank=True,
                                            related_name='creator_set')
    questions = ManyToManyField(Question, blank=True)
    merged_with = ForeignKey('Profile',
                             null=True,
                             blank=True,
                             related_name='former_profile_set',
                             on_delete=CASCADE)
    update_used_codes_timestamp = DateTimeField(null=True, blank=True)
    update_managers_timestamp = DateTimeField(null=True, blank=True)
    vcard = TextField(blank=True)

    @property
    def first_name(self):
        return self.user.first_name

    @property
    def last_name(self):
        return self.user.last_name

    @property
    def email(self):
        return self.user.email

    @property
    def username(self):
        return self.user.username

    def __superiors(self, codegen, known):
        for c in self.received_codes.filter(format=codegen.format,
                                            salt=codegen.salt):
            for o in c.owner_set.all():
                if o not in known:
                    s1 = Profile.__superiors(o, codegen, known)
                    known = s1.union(known)
                    known.add(o)
        return known

    def manages_self(self):
        return self.managed_profiles.filter(id=self.id).exists()

    def managed_others(self):
        return self.managed_profiles.exclude(id=self.id)

    def update_used_codes(self):
        if self.update_used_codes_timestamp is None:
            attempts = self.attempt_set.all()
        else:
            attempts = self.attempt_set.filter(
                start__gte=self.update_used_codes_timestamp)
            self.update_used_codes_timestamp = timezone.now()
        for a in attempts:
            try:
                codegen = a.competitionquestionset.competition.competitor_code_generator
                codes = Code.objects.filter(value=a.access_code,
                                            salt=codegen.salt,
                                            format=codegen.format)
                for c in codes:
                    self.used_codes.add(c)
            except Exception:
                # TODO: handle exception
                pass

    def apply_code_effects(self, codes=None):
        if codes is None:
            codes = self.used_codes
        for c in codes:
            for effect in c.code_effect_set:
                effect.apply(users=[self])

    def update_managers(self, codes=None):
        for c in codes:
            if c.format.code_matches(c.salt, c.value,
                                     {'code_effects': ['let_manage']}):
                for u in c.owner_set:
                    u.managed_profiles.add(self)
            if c.format.code_matches(
                    c.salt, c.value,
                {'code_effects': ['let_manage_recursive']}):
                competitions = Competition.objects.filter(
                    competitor_code_generator__salt=c.salt,
                    competitor_code_generator__format=c.format).unique()
                superiors = set()
                for u in c.owner_set:
                    superiors.add(u)
                    for competition in competitions:
                        superiors = u.__superiors(
                            competition.administrator_code_generator,
                            superiors)
                for superior in superiors:
                    superior.managed_profiles.add(self)

    def update_managed_profiles(self, codes=None):
        if codes is None:
            codes = Code.objects.filter(owner_set=self).unique()
        for c in codes:
            if c.format.code_matches(c.salt, c.value,
                                     {'code_effects': ['let_manage']}):
                for u in c.user_set:
                    u.managers.add(self.user)
            if c.format.code_matches(
                    c.salt, c.value,
                {'code_effects': ['let_manage_recursive']}):
                competitions = Competition.objects.filter(
                    competitor_code_generator__salt=c.salt,
                    competitor_code_generator__format=c.format).unique()
                for u in c.user_set:
                    u.managers.add(self.user)
                    for competition in competitions:
                        for superior in u.__superiors(
                                competition.administrator_code_generator):
                            u.managers.add(superior)

    def merge_to_top(self, limit=None):
        if self.merged_with is not None:
            # detect cycles, merge profiles
            profile = self
            old_profiles = set()
            old_profiles.add(None)
            while profile not in old_profiles and \
                    limit is not None and \
                    len(old_profiles) < limit:
                old_profiles.add(profile)
                prev_profile = profile
                profile = profile.merged_with
            if profile is None:
                profile = prev_profile
            elif profile in old_profiles:
                # this is a cycle. Break it.
                old_profiles.remove(profile)
                profile.merged_with = None
                profile.save()
            else:
                # limit exceeded.
                pass
            old_profiles.remove(None)
            for old_profile in old_profiles:
                old_profile.merged_with = profile
                old_profile.save()
            return profile
        # profile not merged
        return self
Example #4
0
class BMC(CleanSave, TimestampedModel):
    """A `BMC` represents an existing 'baseboard management controller'.  For
    practical purposes in MAAS, this is any addressable device that can control
    the power state of Nodes. The BMC associated with a Node is the one
    expected to control its power.

    Power parameters that apply to all nodes controlled by a BMC are stored
    here in the BMC. Those that are specific to different Nodes on the same BMC
    are stored in the Node model instances.

    :ivar ip_address: This `BMC`'s IP Address.
    :ivar power_type: The power type defines which type of BMC this is.
        Its value must match a power driver class name.
    :ivar power_parameters: Some JSON containing arbitrary parameters this
        BMC's power driver requires to function.
    :ivar objects: The :class:`BMCManager`.
    """
    class Meta(DefaultMeta):
        unique_together = ("power_type", "power_parameters", "ip_address")

    objects = Manager()

    bmcs = BMCManager()

    bmc_type = IntegerField(choices=BMC_TYPE_CHOICES,
                            editable=False,
                            default=BMC_TYPE.DEFAULT)

    ip_address = ForeignKey(StaticIPAddress,
                            default=None,
                            blank=True,
                            null=True,
                            editable=False,
                            on_delete=SET_NULL)

    # The possible choices for this field depend on the power types advertised
    # by the rack controllers.  This needs to be populated on the fly, in
    # forms.py, each time the form to edit a node is instantiated.
    power_type = CharField(max_length=10, null=False, blank=True, default='')

    # JSON-encoded set of parameters for power control, limited to 32kiB when
    # encoded as JSON. These apply to all Nodes controlled by this BMC.
    power_parameters = JSONObjectField(max_length=(2**15),
                                       blank=True,
                                       default='')

    # Rack controllers that have access to the BMC by routing instead of
    # having direct layer 2 access.
    routable_rack_controllers = ManyToManyField(
        "RackController",
        blank=True,
        editable=True,
        through="BMCRoutableRackControllerRelationship",
        related_name="routable_bmcs")

    # Values for Pod's.
    #  1. Name of the Pod.
    #  2. List of architectures that a Pod supports.
    #  3. Capabilities that the Pod supports.
    #  4. Total cores in the Pod.
    #  5. Fastest CPU speed in the Pod.
    #  6. Total amount of memory in the Pod.
    #  7. Total about in bytes of local storage available in the Pod.
    #  8. Total number of available local disks in the Pod.
    #  9. The resource pool machines in the pod should belong to by default.
    #  10. The zone of the Pod.
    #  11. The tags of the Pod.
    #  12. CPU over commit ratio multiplier ('over_commit' capabilities).
    #  13. Memory over commit ratio multiplier ('over_commit' capabilities).
    name = CharField(max_length=255, default='', blank=True, unique=True)
    architectures = ArrayField(TextField(),
                               blank=True,
                               null=True,
                               default=list)
    capabilities = ArrayField(TextField(), blank=True, null=True, default=list)
    cores = IntegerField(blank=False, null=False, default=0)
    cpu_speed = IntegerField(blank=False, null=False, default=0)  # MHz
    memory = IntegerField(blank=False, null=False, default=0)
    local_storage = BigIntegerField(  # Bytes
        blank=False, null=False, default=0)
    local_disks = IntegerField(blank=False, null=False, default=-1)
    iscsi_storage = BigIntegerField(  # Bytes
        blank=False, null=False, default=-1)
    default_pool = ForeignKey(ResourcePool,
                              default=None,
                              null=True,
                              blank=True,
                              editable=True,
                              on_delete=PROTECT)
    zone = ForeignKey(Zone,
                      verbose_name="Physical zone",
                      default=get_default_zone,
                      editable=True,
                      db_index=True,
                      on_delete=SET_DEFAULT)
    tags = ArrayField(TextField(), blank=True, null=True, default=list)
    cpu_over_commit_ratio = FloatField(default=1,
                                       validators=[MinValueValidator(0)])
    memory_over_commit_ratio = FloatField(default=1,
                                          validators=[MinValueValidator(0)])

    def __str__(self):
        return "%s (%s)" % (self.id,
                            self.ip_address if self.ip_address else "No IP")

    def _as(self, model):
        """Create a `model` that shares underlying storage with `self`.

        In other words, the newly returned object will be an instance of
        `model` and its `__dict__` will be `self.__dict__`. Not a copy, but a
        reference to, so that changes to one will be reflected in the other.
        """
        new = object.__new__(model)
        new.__dict__ = self.__dict__
        return new

    def as_bmc(self):
        """Return a reference to self that behaves as a `BMC`."""
        return self._as(BMC)

    def as_pod(self):
        """Return a reference to self that behaves as a `Pod`."""
        return self._as(Pod)

    _as_self = {
        BMC_TYPE.BMC: as_bmc,
        BMC_TYPE.POD: as_pod,
    }

    def as_self(self):
        """Return a reference to self that behaves as its own type."""
        return self._as_self[self.bmc_type](self)

    def delete(self):
        """Delete this BMC."""
        maaslog.info("%s: Deleting BMC", self)
        super(BMC, self).delete()

    def save(self, *args, **kwargs):
        """Save this BMC."""
        super(BMC, self).save(*args, **kwargs)
        # We let name be blank for the initial save, but fix it before the
        # save completes.  This is because set_random_name() operates by
        # trying to re-save the BMC with a random hostname, and retrying until
        # there is no conflict.
        if self.name == '':
            self.set_random_name()

    def set_random_name(self):
        """Set a random `name`."""
        while True:
            self.name = petname.Generate(2, "-")
            try:
                self.save()
            except ValidationError:
                pass
            else:
                break

    def clean(self):
        """ Update our ip_address if the address extracted from our power
        parameters has changed. """
        new_ip = BMC.extract_ip_address(self.power_type, self.power_parameters)
        current_ip = None if self.ip_address is None else self.ip_address.ip
        # Set the ip_address field.  If we have a bracketed address, assume
        # it's IPv6, and strip the brackets.
        if new_ip and new_ip.startswith('[') and new_ip.endswith(']'):
            new_ip = new_ip[1:-1]
        if new_ip != current_ip:
            if new_ip is None:
                self.ip_address = None
            else:
                # Update or create a StaticIPAddress for the new IP.
                try:
                    # This atomic block ensures that an exception within will
                    # roll back only this block's DB changes. This allows us to
                    # swallow exceptions in here and keep all changes made
                    # before or after this block is executed.
                    with transaction.atomic():
                        subnet = Subnet.objects.get_best_subnet_for_ip(new_ip)
                        (self.ip_address,
                         _) = StaticIPAddress.objects.get_or_create(
                             ip=new_ip,
                             defaults={
                                 'alloc_type': IPADDRESS_TYPE.STICKY,
                                 'subnet': subnet,
                             })
                except Exception as error:
                    maaslog.info(
                        "BMC could not save extracted IP "
                        "address '%s': '%s'", new_ip, error)

    @staticmethod
    def scope_power_parameters(power_type, power_params):
        """Separate the global, bmc related power_parameters from the local,
        node-specific ones."""
        if not power_type:
            # If there is no power type, treat all params as node params.
            return ({}, power_params)
        power_driver = PowerDriverRegistry.get_item(power_type)
        if power_driver is None:
            # If there is no power driver, treat all params as node params.
            return ({}, power_params)
        power_fields = power_driver.settings
        if not power_fields:
            # If there is no parameter info, treat all params as node params.
            return ({}, power_params)
        bmc_params = {}
        node_params = {}
        for param_name in power_params:
            power_field = power_driver.get_setting(param_name)
            if (power_field and power_field.get('scope') == SETTING_SCOPE.BMC):
                bmc_params[param_name] = power_params[param_name]
            else:
                node_params[param_name] = power_params[param_name]
        return (bmc_params, node_params)

    @staticmethod
    def extract_ip_address(power_type, power_parameters):
        """ Extract the ip_address from the power_parameters. If there is no
        power_type, no power_parameters, or no valid value provided in the
        power_address field, returns None. """
        if not power_type or not power_parameters:
            # Nothing to extract.
            return None
        power_driver = PowerDriverRegistry.get_item(power_type)
        if power_driver is None:
            maaslog.warning("No power driver for power type %s" % power_type)
            return None
        power_type_parameters = power_driver.settings
        if not power_type_parameters:
            maaslog.warning("No power driver settings for power type %s" %
                            power_type)
            return None
        ip_extractor = power_driver.ip_extractor
        if not ip_extractor:
            maaslog.info("No IP extractor configured for power type %s. "
                         "IP will not be extracted." % power_type)
            return None
        field_value = power_parameters.get(ip_extractor.get('field_name'))
        if not field_value:
            maaslog.warning("IP extractor field_value missing for %s" %
                            power_type)
            return None
        extraction_pattern = ip_extractor.get('pattern')
        if not extraction_pattern:
            maaslog.warning("IP extractor extraction_pattern missing for %s" %
                            power_type)
            return None
        match = re.match(extraction_pattern, field_value)
        if match:
            return match.group('address')
        # no match found - return None
        return None

    def get_layer2_usable_rack_controllers(self, with_connection=True):
        """Return a list of `RackController`'s that have the ability to access
        this `BMC` directly through a layer 2 connection."""
        ip_address = self.ip_address
        if ip_address is None or ip_address.ip is None or ip_address.ip == '':
            return set()

        # The BMC has a valid StaticIPAddress set. Make sure that the subnet
        # is correct for that BMC.
        subnet = Subnet.objects.get_best_subnet_for_ip(ip_address.ip)
        if subnet is not None and self.ip_address.subnet_id != subnet.id:
            self.ip_address.subnet = subnet
            self.ip_address.save()

        # Circular imports.
        from maasserver.models.node import RackController
        return RackController.objects.filter_by_url_accessible(
            ip_address.ip, with_connection=with_connection)

    def get_routable_usable_rack_controllers(self, with_connection=True):
        """Return a list of `RackController`'s that have the ability to access
        this `BMC` through a route on the rack controller."""
        routable_racks = [
            relationship.rack_controller
            for relationship in (self.routable_rack_relationships.all(
            ).select_related("rack_controller")) if relationship.routable
        ]
        if with_connection:
            conn_rack_ids = [client.ident for client in getAllClients()]
            return [
                rack for rack in routable_racks
                if rack.system_id in conn_rack_ids
            ]
        else:
            return routable_racks

    def get_usable_rack_controllers(self, with_connection=True):
        """Return a list of `RackController`'s that have the ability to access
        this `BMC` either using layer2 or routable if no layer2 are available.
        """
        racks = self.get_layer2_usable_rack_controllers(
            with_connection=with_connection)
        if len(racks) == 0:
            # No layer2 routable rack controllers. Use routable rack
            # controllers.
            racks = self.get_routable_usable_rack_controllers(
                with_connection=with_connection)
        return racks

    def get_client_identifiers(self):
        """Return a list of identifiers that can be used to get the
        `rpc.common.Client` for this `BMC`.

        :raise NoBMCAccessError: Raised when no rack controllers have access
            to this `BMC`.
        """
        rack_controllers = self.get_usable_rack_controllers()
        identifers = [controller.system_id for controller in rack_controllers]
        return identifers

    def is_accessible(self):
        """If the BMC is accessible by at least one rack controller."""
        racks = self.get_usable_rack_controllers(with_connection=False)
        return len(racks) > 0

    def update_routable_racks(self, routable_racks_ids,
                              non_routable_racks_ids):
        """Set the `routable_rack_controllers` relationship to the new
        information."""
        BMCRoutableRackControllerRelationship.objects.filter(
            bmc=self.as_bmc()).delete()
        self._create_racks_relationship(routable_racks_ids, True)
        self._create_racks_relationship(non_routable_racks_ids, False)

    def _create_racks_relationship(self, rack_ids, routable):
        """Create `BMCRoutableRackControllerRelationship` for list of
        `rack_ids` and wether they are `routable`."""
        # Circular imports.
        from maasserver.models.node import RackController
        for rack_id in rack_ids:
            try:
                rack = RackController.objects.get(system_id=rack_id)
            except RackController.DoesNotExist:
                # Possible it was delete before this call, but very very rare.
                pass
            BMCRoutableRackControllerRelationship(bmc=self,
                                                  rack_controller=rack,
                                                  routable=routable).save()
Example #5
0
class Channel(Model):
    title = CharField(max_length=50)
    tags = ManyToManyField(Tag)
Example #6
0
class AppRelease(TranslatableModel):
    version = CharField(max_length=256,
                        verbose_name=_('Version'),
                        help_text=_('Version follows Semantic Versioning'))
    app = ForeignKey('App',
                     on_delete=CASCADE,
                     verbose_name=_('App'),
                     related_name='releases')
    # dependencies
    php_extensions = ManyToManyField(
        'PhpExtension',
        blank=True,
        through='PhpExtensionDependency',
        verbose_name=_('PHP extension dependency'))
    databases = ManyToManyField('Database',
                                blank=True,
                                through='DatabaseDependency',
                                verbose_name=_('Database dependency'))
    licenses = ManyToManyField('License', verbose_name=_('License'))
    shell_commands = ManyToManyField(
        'ShellCommand', blank=True, verbose_name=_('Shell command dependency'))
    php_version_spec = CharField(max_length=256,
                                 verbose_name=_('PHP version requirement'))
    platform_version_spec = CharField(
        max_length=256, verbose_name=_('Platform version requirement'))
    raw_php_version_spec = CharField(
        max_length=256, verbose_name=_('PHP version requirement (raw)'))
    raw_platform_version_spec = CharField(
        max_length=256, verbose_name=_('Platform version requirement (raw)'))
    min_int_size = IntegerField(blank=True,
                                default=32,
                                verbose_name=_('Minimum Integer bits'),
                                help_text=_('e.g. 32 for 32bit Integers'))
    download = URLField(max_length=256,
                        blank=True,
                        verbose_name=_('Archive download URL'))
    created = DateTimeField(auto_now_add=True,
                            editable=False,
                            verbose_name=_('Created at'))
    last_modified = DateTimeField(auto_now=True,
                                  editable=False,
                                  db_index=True,
                                  verbose_name=_('Updated at'))
    signature = TextField(
        verbose_name=_('Signature'),
        help_text=_('A signature using the app\'s certificate'))
    signature_digest = CharField(max_length=256,
                                 verbose_name=_('Signature hashing algorithm'))
    translations = TranslatedFields(changelog=TextField(
        verbose_name=_('Changelog'),
        help_text=_('The release changelog. Can contain Markdown'),
        default=''))
    is_nightly = BooleanField(verbose_name=_('Nightly'), default=False)

    class Meta:
        verbose_name = _('App release')
        verbose_name_plural = _('App releases')
        unique_together = (('app', 'version', 'is_nightly'), )
        ordering = ['-version']

    def can_update(self, user: User) -> bool:
        return self.app.owner == user or user in self.app.co_maintainers.all()

    def can_delete(self, user: User) -> bool:
        return self.can_update(user)

    def __str__(self) -> str:
        return '%s %s' % (self.app, self.version)

    def is_compatible(self, platform_version, inclusive=False):
        """Checks if a release is compatible with a platform version

        :param platform_version: the platform version, not required to be
                                 semver compatible
        :param inclusive: if True the check will also return True if an app
                          requires 9.0.1 and the given platform version is 9.0
        :return: True if compatible, otherwise false
        """

        min_version = Version(pad_min_version(platform_version))
        spec = Spec(self.platform_version_spec)
        if inclusive:
            max_version = Version(pad_max_inc_version(platform_version))
            return (min_version in spec or max_version in spec)
        else:
            return min_version in spec

    @property
    def is_unstable(self):
        return self.is_nightly or '-' in self.version
Example #7
0
class Image(Model, AuthorMixin):
    img = ImageField(upload_to='images/%Y/%m')
    title = CharField(max_length=255, blank=True, null=True)
    width = PositiveIntegerField(blank=True, null=True)
    height = PositiveIntegerField(blank=True, null=True)

    authors = ManyToManyField(Author, related_name='image_authors')
    tags = ManyToManyField('Tag')

    created_at = DateTimeField(auto_now_add=True)
    updated_at = DateTimeField(auto_now=True)

    SIZES = {'large': (1600, 900), 'medium': (800, 600), 'square': (250, 250)}

    JPG_FORMATS = (
        'jpg',
        'JPG',
        'JPEG',
        'jpeg',
    )
    GIF_FORMATS = (
        'gif',
        'GIF',
    )

    IMAGE_FORMATS = '.(jpg|JPEG|jpeg|JPG|gif|png|PNG|tiff|tif|dng)'

    THUMBNAIL_SIZE = 'square'

    AuthorModel = Author

    def is_gif(self):
        """Returns true if image is a gif."""
        return self.get_extension() in self.GIF_FORMATS

    def get_filename(self):
        """Returns the image filename."""
        return os.path.basename(self.img.name)

    def get_name(self):
        """Returns the filename without its extension."""
        return os.path.splitext(self.img.name)[0]

    def get_extension(self):
        """Returns the file extension."""
        ext = os.path.splitext(self.img.name)[1]
        if ext:
            # Remove period from extension
            return ext[1:]
        return ext

    def get_absolute_url(self):
        """Returns the full size image URL."""
        return settings.MEDIA_URL + str(self.img)

    def get_medium_url(self):
        """Returns the medium size image URL."""
        if self.is_gif():
            return self.get_absolute_url()
        return '%s%s-%s.jpg' % (settings.MEDIA_URL, self.get_name(), 'medium')

    def get_thumbnail_url(self):
        """Returns the thumbnail URL."""
        return '%s%s-%s.jpg' % (settings.MEDIA_URL, self.get_name(),
                                self.THUMBNAIL_SIZE)

    # Overriding
    def save(self, **kwargs):
        """Custom save method to process thumbnails and save image dimensions."""
        is_new = self.pk is None

        if is_new:
            # Make filenames lowercase
            self.img.name = self.img.name.lower()

        # Call super method
        super(Image, self).save(**kwargs)

        if is_new and self.img:
            data = self.img.read()

            if not data:
                return

            image = Img.open(StringIO.StringIO(data))

            self.width, self.height = image.size

            super(Image, self).save()

            name = self.get_name()
            ext = self.get_extension()

            for size in self.SIZES.keys():
                self.save_thumbnail(image, self.SIZES[size], name, size, ext)

    def save_thumbnail(self, image, size, name, label, file_type):
        """Processes and saves a resized thumbnail version of the image."""
        width, height = size
        (imw, imh) = image.size

        # If image is larger than thumbnail size, resize image
        if (imw > width) or (imh > height):
            image.thumbnail(size, Img.ANTIALIAS)

        # Attach new thumbnail label to image filename
        name = "%s-%s.jpg" % (name, label)

        # Image.save format takes JPEG not jpg
        if file_type in self.JPG_FORMATS:
            file_type = 'JPEG'

        # Write new thumbnail to StringIO object
        image_io = StringIO.StringIO()
        image.save(image_io, format=file_type, quality=75)

        # Convert StringIO object to Django File object
        thumb_file = InMemoryUploadedFile(image_io, None, name, 'image/jpeg',
                                          image_io.len, None)

        # Save the new file to the default storage system
        default_storage.save(name, thumb_file)

    def save_tags(self, tag_ids):
        self.tags.clear()
        for tag_id in tag_ids:
            try:
                tag = Tag.objects.get(id=int(tag_id))
                self.tags.add(tag)
            except Tag.DoesNotExist:
                pass
Example #8
0
class UserProfile(AbstractUser):
    USER_TYPE_CHOICES = (
        (0, "后台用户"),
        (1, "前台用户"),
    )
    objects = UserManager()
    username = CharField(max_length=150,
                         unique=True,
                         db_index=True,
                         verbose_name='用户账号')
    secret = CharField(max_length=255, default=uuid4, verbose_name='加密秘钥')
    email = CharField(max_length=255, verbose_name="邮箱", null=True, blank=True)
    mobile = CharField(max_length=255,
                       verbose_name="电话",
                       null=True,
                       blank=True)
    avatar = TextField(verbose_name="头像", null=True, blank=True)
    name = CharField(max_length=40, verbose_name="姓名")
    gender = CharField(max_length=8, verbose_name="性别", null=True, blank=True)
    remark = TextField(verbose_name="备注", null=True)
    user_type = IntegerField(default=0, verbose_name="用户类型")
    post = ManyToManyField(to='Post', verbose_name='关联岗位', db_constraint=False)
    role = ManyToManyField(to='Role', verbose_name='关联角色', db_constraint=False)
    dept = ForeignKey(to='Dept',
                      verbose_name='归属部门',
                      on_delete=CASCADE,
                      db_constraint=False,
                      null=True,
                      blank=True)
    dept_belong_id = CharField(max_length=64,
                               verbose_name="数据归属部门",
                               null=True,
                               blank=True)
    create_datetime = CreateDateTimeField()
    update_datetime = UpdateDateTimeField()

    @property
    def get_user_interface_dict(self):
        interface_dict = cache.get(f'permission_interface_dict{self.username}',
                                   {})
        if not interface_dict:
            for ele in self.role.filter(status='1', menu__status='1').values(
                    'menu__interface_path',
                    'menu__interface_method').distinct():
                interface_path = ele.get('menu__interface_path')
                if interface_path is None or interface_path == '':
                    continue
                if ele.get('menu__interface_method') in interface_dict:
                    interface_dict[ele.get('menu__interface_method',
                                           '')].append(interface_path)
                else:
                    interface_dict[ele.get('menu__interface_method',
                                           '')] = [interface_path]
            cache.set(f'permission_interface_dict_{self.username}',
                      interface_dict, 84600)
        return interface_dict

    @property
    def delete_cache(self):
        """
        清空缓存中的接口列表
        :return:
        """
        return cache.delete(f'permission_interface_dict_{self.username}')

    class Meta:
        verbose_name = '用户管理'
        verbose_name_plural = verbose_name

    def __str__(self):
        if self.name:
            return f"{self.username}({self.name})"
        return f"{self.username}"
Example #9
0
class EquipmentDataField(Model):
    """Equipment Data Field."""

    RELATED_NAME = 'equipment_data_fields'
    RELATED_QUERY_NAME = 'equipment_data_field'

    DEFAULT_UPPER_NUMERIC_NULL = 2**30  # << MaxInt = 2 ** 31 - 1
    DEFAULT_LOWER_NUMERIC_NULL = -DEFAULT_UPPER_NUMERIC_NULL

    equipment_general_type = \
        ForeignKey(
            to=EquipmentGeneralType,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=False,
            null=False,
            on_delete=PROTECT)

    name = \
        CharField(
            verbose_name='Equipment Data Field',
            blank=False,
            null=False,
            db_index=True,
            max_length=MAX_CHAR_LEN)

    equipment_data_field_type = \
        ForeignKey(
            to=EquipmentDataFieldType,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=False,
            null=False,
            on_delete=PROTECT)

    logical_data_type = \
        ForeignKey(
            to=LogicalDataType,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=True,
            null=True,
            on_delete=PROTECT)

    numeric_measurement_unit = \
        ForeignKey(
            to=NumericMeasurementUnit,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=True,
            null=True,
            on_delete=PROTECT)

    lower_numeric_null = \
        FloatField(
            blank=False,
            null=False,
            default=DEFAULT_LOWER_NUMERIC_NULL)

    upper_numeric_null = \
        FloatField(
            blank=False,
            null=False,
            default=DEFAULT_UPPER_NUMERIC_NULL)

    min_val = \
        FloatField(
            blank=True,
            null=True)

    max_val = \
        FloatField(
            blank=True,
            null=True)

    equipment_unique_types = \
        ManyToManyField(
            to='EquipmentUniqueType',
            related_name=RELATED_NAME + '_reverse',
            related_query_name=RELATED_QUERY_NAME,
            blank=True)

    class Meta:
        """Metadata."""

        verbose_name = 'Equipment Data Field'
        verbose_name_plural = 'Equipment Data Fields'

        unique_together = 'equipment_general_type', 'name'

        ordering = 'equipment_general_type', 'name'

    def __str__(self):
        """Return string repr."""
        return (
            (f'{self.equipment_general_type.name.upper()} '
             f'[{self.equipment_data_field_type.name}] '
             f'{self.name} [') + (self.logical_data_type.name
                                  if self.logical_data_type else 'UNTYPED') +
            (
                f', unit {self.numeric_measurement_unit.name.upper()}'
                if self.numeric_measurement_unit
                and self.numeric_measurement_unit.name  # noqa: E501
                else '') +
            f', nulls ({self.lower_numeric_null}, {self.upper_numeric_null})'
            +  # noqa: E501
            ('' if self.min_val is None else f', min {self.min_val}') +
            ('' if self.max_val is None else f', max {self.max_val}') + ']')

    def save(self, *args, **kwargs):
        """Save."""
        self.name = clean_lower_str(self.name)
        super().save(*args, **kwargs)
Example #10
0
class Index(Model):
    name = CharField(max_length=60)
    country = CharField(max_length=60)
    stocks = ManyToManyField(Stock, through="StockOnIndex")
Example #11
0
class DiscordUser(Model):
    id = IntegerField(primary_key=True)
    has_elements = ManyToManyField(Element)
    last_ingredients = TextField(null=True)
Example #12
0
class Game(Model):
    """game model"""

    bgg_id = PositiveIntegerField(primary_key=True)
    name = CharField(max_length=255, db_index=True)
    alt_name = JSONField(default=list)
    year = SmallIntegerField(blank=True, null=True, db_index=True)
    description = TextField(blank=True, null=True)

    designer = ManyToManyField("Person",
                               blank=True,
                               related_name="designer_of")
    artist = ManyToManyField("Person", blank=True, related_name="artist_of")
    # publisher = ListField(CharField(), blank=True)

    url = URLField(blank=True, null=True)
    image_url = JSONField(default=list)
    video_url = JSONField(default=list)
    external_link = JSONField(default=list)
    # list_price = CharField(max_length=100, blank=True, null=True)

    min_players = PositiveSmallIntegerField(blank=True,
                                            null=True,
                                            db_index=True)
    max_players = PositiveSmallIntegerField(blank=True,
                                            null=True,
                                            db_index=True)
    min_players_rec = PositiveSmallIntegerField(blank=True,
                                                null=True,
                                                db_index=True)
    max_players_rec = PositiveSmallIntegerField(blank=True,
                                                null=True,
                                                db_index=True)
    min_players_best = PositiveSmallIntegerField(blank=True,
                                                 null=True,
                                                 db_index=True)
    max_players_best = PositiveSmallIntegerField(blank=True,
                                                 null=True,
                                                 db_index=True)
    min_age = PositiveSmallIntegerField(blank=True, null=True, db_index=True)
    max_age = PositiveSmallIntegerField(blank=True, null=True, db_index=True)
    min_age_rec = FloatField(blank=True, null=True, db_index=True)
    max_age_rec = FloatField(blank=True, null=True, db_index=True)
    min_time = PositiveSmallIntegerField(blank=True, null=True, db_index=True)
    max_time = PositiveSmallIntegerField(blank=True, null=True, db_index=True)

    game_type = ManyToManyField("GameType", blank=True, related_name="games")
    category = ManyToManyField("Category", blank=True, related_name="games")
    mechanic = ManyToManyField("Mechanic", blank=True, related_name="games")
    cooperative = BooleanField(default=False, db_index=True)
    compilation = BooleanField(default=False, db_index=True)
    compilation_of = ManyToManyField("self",
                                     symmetrical=False,
                                     blank=True,
                                     related_name="contained_in")
    # family = ListField(CharField(), blank=True)
    # expansion = ListField(CharField(), blank=True)
    implements = ManyToManyField("self",
                                 symmetrical=False,
                                 blank=True,
                                 related_name="implemented_by")
    integrates_with = ManyToManyField("self", symmetrical=True, blank=True)

    bgg_rank = PositiveIntegerField(blank=True, null=True, db_index=True)
    num_votes = PositiveIntegerField(default=0, db_index=True)
    avg_rating = FloatField(blank=True, null=True, db_index=True)
    stddev_rating = FloatField(blank=True, null=True, db_index=True)
    bayes_rating = FloatField(blank=True, null=True, db_index=True)

    rec_rank = PositiveIntegerField(blank=True, null=True, db_index=True)
    rec_rating = FloatField(blank=True, null=True, db_index=True)
    rec_stars = FloatField(blank=True, null=True, db_index=True)

    complexity = FloatField(blank=True, null=True, db_index=True)
    language_dependency = FloatField(blank=True, null=True, db_index=True)
    kennerspiel_score = FloatField(blank=True, null=True, db_index=True)

    freebase_id = JSONField(default=list)
    wikidata_id = JSONField(default=list)
    wikipedia_id = JSONField(default=list)
    dbpedia_id = JSONField(default=list)
    luding_id = JSONField(default=list)
    spielen_id = JSONField(default=list)
    bga_id = JSONField(default=list)

    def highest_ranking(self, ranking_type=Ranking.BGG):
        """Find the highest ever rank of the given type."""
        return (
            # pylint: disable=no-member
            self.ranking_set.filter(ranking_type=ranking_type
                                    ).order_by("rank", "-date").first())

    @property
    def highest_ranking_bgg(self):
        """Highest BGG ranking."""
        return self.highest_ranking(ranking_type=Ranking.BGG)

    @property
    def highest_ranking_factor(self):
        """Highest factor model ranking."""
        return self.highest_ranking(ranking_type=Ranking.FACTOR)

    @property
    def highest_ranking_similarity(self):
        """Highest similarity model ranking."""
        return self.highest_ranking(ranking_type=Ranking.SIMILARITY)

    class Meta:
        """meta"""

        ordering = ("-rec_rating", "-bayes_rating", "-avg_rating")
        indexes = (Index(fields=("-rec_rating", "-bayes_rating",
                                 "-avg_rating")), )

    def __str__(self):
        return str(self.name)
Example #13
0
class AbstractMailingList(CremeEntity):
    name = CharField(_('Name of the mailing list'), max_length=80)
    children = ManyToManyField(
        settings.EMAILS_MLIST_MODEL,
        verbose_name=_('Child mailing lists'),
        symmetrical=False,
        related_name='parents_set',
        editable=False,
    )
    contacts = ManyToManyField(
        settings.PERSONS_CONTACT_MODEL,
        verbose_name=_('Contacts recipients'),
        editable=False,
    )
    organisations = ManyToManyField(
        settings.PERSONS_ORGANISATION_MODEL,
        verbose_name=_('Organisations recipients'),
        editable=False,
    )

    creation_label = _('Create a mailing list')
    save_label = _('Save the mailing list')

    class Meta:
        abstract = True
        # manager_inheritance_from_future = True
        app_label = 'emails'
        verbose_name = _('Mailing list')
        verbose_name_plural = _('Mailing lists')
        ordering = ('name', )

    def __str__(self):
        return self.name

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

    @staticmethod
    def get_create_absolute_url():
        return reverse('emails__create_mlist')

    def get_edit_absolute_url(self):
        return reverse('emails__edit_mlist', args=(self.id, ))

    @staticmethod
    def get_lv_absolute_url():
        return reverse('emails__list_mlists')

    def already_in_parents(self, other_ml_id):
        parents = self.parents_set.all()

        for parent in parents:
            if parent.id == other_ml_id:
                return True

        for parent in parents:
            if parent.already_in_parents(other_ml_id):
                return True

        return False

    def already_in_children(self, other_ml_id):
        children = self.children.all()

        for child in children:
            if child.id == other_ml_id:
                return True

        for child in children:
            if child.already_in_children(other_ml_id):
                return True

        return False

    def get_family(self):
        """Return a dictionary<pk: MailingList> with self and all children,
         small children etc...
         """
        family = {}
        self.get_family_aux(family)

        return family

    def get_family_aux(self, dic):
        dic[self.id] = self

        for child in self.children.filter(is_deleted=False):
            child.get_family_aux(dic)
Example #14
0
class Test(Model):

    id = UUIDField(
        primary_key=True,
        default=uuid4,
        editable=False,
    )

    label = CharField(
        verbose_name=_("label"),
        max_length=32,
        blank=False,
        db_index=True,
        unique=True,
    )

    category = ForeignKey(
        verbose_name=_("category"),
        related_name="test_set",
        to=Category,
        null=True,
        blank=True,
        db_index=True,
        on_delete=PROTECT,
    )

    theme_set = ManyToManyField(
        verbose_name=_("theme_set"),
        related_name="test_set",
        to=Theme,
        blank=True,
    )

    category2 = ForeignKey(
        verbose_name=_("category 2"),
        related_name="test2_set",
        to=Category,
        null=True,
        blank=True,
        db_index=True,
        on_delete=CASCADE,
    )

    theme_set2 = ManyToManyField(
        verbose_name=_("theme_set 2"),
        related_name="test2_set",
        to=Theme,
        blank=True,
    )

    category3 = ForeignKey(
        verbose_name=_("category 3"),
        related_name="test3_set",
        to=Category,
        null=True,
        blank=True,
        db_index=True,
        on_delete=CASCADE,
    )

    theme_set3 = ManyToManyField(
        verbose_name=_("theme_set 3"),
        related_name="test3_set",
        to=Theme,
        blank=True,
    )

    category4 = ForeignKey(
        verbose_name=_("category 4"),
        related_name="test4_set",
        to=Category,
        null=True,
        blank=True,
        db_index=True,
        on_delete=CASCADE,
    )

    theme_set4 = ManyToManyField(
        verbose_name=_("theme_set 4"),
        related_name="test4_set",
        to=Theme,
        blank=True,
    )

    category5 = ForeignKey(
        verbose_name=_("category 5"),
        related_name="test5_set",
        to=Category,
        null=True,
        blank=True,
        db_index=True,
        on_delete=CASCADE,
    )

    theme_set5 = ManyToManyField(
        verbose_name=_("theme_set 5"),
        related_name="test5_set",
        to=Theme,
        blank=True,
    )

    category6 = ForeignKey(
        verbose_name=_("category 6"),
        related_name="test6_set",
        to=Category,
        null=True,
        blank=True,
        db_index=True,
        on_delete=CASCADE,
    )

    theme_set6 = ManyToManyField(
        verbose_name=_("theme_set 6"),
        related_name="test6_set",
        to=Theme,
        blank=True,
    )

    tag_set = ManyToManyField(
        verbose_name=_("theme_set"),
        related_name="test_set",
        to=Tag,
        through="Mapping",
        blank=True,
    )

    number = PositiveSmallIntegerField(
        verbose_name=_("number"),
        unique=True,
    )

    percent = DecimalField(
        verbose_name=_("percent"),
        max_digits=5,
        decimal_places=2,
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
        blank=True,
        null=True,
    )

    progress = FloatField(
        verbose_name=_("progress"),
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)],
        blank=True,
        null=True,
    )

    grade = IntegerField(
        verbose_name=_("grade"),
        validators=[MinValueValidator(-10),
                    MaxValueValidator(10)],
        blank=True,
        null=True,
    )

    slug = SlugField(
        unique=True,
        max_length=32,
        editable=True,
        db_index=True,
    )

    owner = EmailField(
        verbose_name=_("owner"),
        blank=True,
        null=True,
    )

    url = URLField(
        verbose_name=_("url"),
        blank=True,
        null=True,
    )

    key = UUIDField(default=uuid4, )

    description = TextField(verbose_name=_("description"), )

    active = BooleanField(verbose_name=_("active"), )

    highlight = NullBooleanField(verbose_name=_("highlight"), )

    creation_date = DateField(
        verbose_name=_("creation date"),
        auto_now_add=True,
    )

    last_modification_date = DateField(
        verbose_name=_("last modification date"),
        auto_now=True,
    )

    random_date = DateField(
        verbose_name=_("random date"),
        blank=True,
        null=True,
    )

    creation_datetime = DateTimeField(
        verbose_name=_("creation datetime"),
        auto_now_add=True,
    )

    last_modification_datetime = DateTimeField(
        verbose_name=_("last modification datetime"),
        auto_now=True,
    )

    random_datetime = DateTimeField(
        verbose_name=_("random datetime"),
        blank=True,
        null=True,
    )

    duration = DurationField(
        verbose_name=_("duration"),
        blank=True,
        null=True,
    )

    creation_time = TimeField(
        verbose_name=_("creation time"),
        auto_now_add=True,
    )

    last_modification_time = TimeField(
        verbose_name=_("last modification time"),
        auto_now=True,
    )

    random_time = TimeField(
        verbose_name=_("random time"),
        blank=True,
        null=True,
    )

    ip = GenericIPAddressField(
        verbose_name=_("IP v4 ou 6"),
        protocol="both",
        blank=True,
        null=True,
    )

    ipv4 = GenericIPAddressField(
        verbose_name=_("IP v4 as is"),
        protocol="IPv4",
        blank=True,
        null=True,
    )

    ipv6_forced = GenericIPAddressField(
        verbose_name=_("IP v6 (ipv4 will be converted)"),
        protocol="both",
        unpack_ipv4=True,
        blank=True,
        null=True,
    )

    ipv6 = GenericIPAddressField(
        verbose_name=_("IP v6"),
        protocol="IPv6",
        blank=True,
        null=True,
    )

    raw_data = BinaryField(
        verbose_name=_("raw data"),
        max_length=127,
        editable=True,
        blank=True,
        null=True,
    )

    def compute_upload_path(current_object, sub_path, filename):
        """Describe the image storage path"""
        today = now()
        return str(
            Path.joinpath(*list(
                map(
                    Path,
                    (
                        current_object._meta.app_label,  # pylint: disable=protected-access
                        current_object._meta.model_name,  # pylint: disable=protected-access
                        sub_path,
                        str(today.year),
                        str(today.month),
                        str(uuid4()) + Path(filename).suffix)))))

    file = FileField(
        verbose_name=_("file"),
        max_length=256,
        upload_to=partial(compute_upload_path, subpath="file"),
        null=True,
        blank=True,
    )

    image = ImageField(
        verbose_name=_("image"),
        max_length=256,
        upload_to=partial(compute_upload_path, subpath="image"),
        null=True,
        blank=True,
    )

    path = FilePathField(
        verbose_name=_("path"),
        path=settings.STATIC_PATH,
    )

    def __str__(self):
        """Return a string that represent the current object to an end user."""
        return self.label

    class Meta:  # pylint: disable=too-few-public-methods
        """Test Meta class"""

        verbose_name = _("test")
        verbose_name_plural = _("tests")
        ordering = ("number", )
Example #15
0
class Product(TimeStampedModel):
    title = CharField(_("Product Name"), max_length=255, null=True, blank=False)
    slug = SlugField(unique=True, null=True, blank=True, max_length=500)
    description = RichTextUploadingField()
    price = DecimalField(decimal_places=2, max_digits=20, default=0.99)
    comments = GenericRelation(Comment)
    added_by = CharField(_("added_by"), max_length=255, null=True, blank=True)
    inventory = IntegerField(null=True, blank=True, default=0)
    categories = ManyToManyField("category.Category", help_text="Categorize this Product.")
    tags = ManyToManyField("category.Tag", help_text="Tag this Product.")
    featured = BooleanField(default=False)
    old_stock = BooleanField(default=False)
    draft = BooleanField(default=False)
    product_id = CharField(max_length=255, null=True, blank=True)
    is_digital = BooleanField(default=False)

    objects = ProductManager()

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

    @property
    def old_product(self):
        created_on = self.created.day
        ttdays = datetime.timedelta(days=60)
        if created_on > ttdays:
            return old_stock == True
        return old_stock == False

    def get_downloads(self):
        qs = self.productfile_set.all()
        return qs

    def get_image_url(self):
        img = self.productimage_set.first()
        if img:
            return img.image.url
        return img



    @property
    def get_related_products_by_tags(self):
        return Product.objects.filter(tags__in=self.tags.all())[0:4]

    @staticmethod
    def autocomplete_search_fields():
        return "title"

    class Meta:
        managed = True
        verbose_name = "Product"
        verbose_name_plural = "Products"
        ordering = ["title", "-created"]

    def get_absolute_url(self):
        return f"/shop/products/{self.slug}/{self.id}"

    def get_update_url(self):
        return f"{self.get_absolute_url}/update"

    def get_delete_url(self):
        return f"{self.get_absolute_url}/delete"
Example #16
0
class Conversation(BaseModel):
    participants = ManyToManyField(User)
    type = enum.EnumField(ConversationType,
                          default=ConversationType.ONE_ON_ONE)

    name = MaxLengthCharField(null=True)
Example #17
0
class EquipmentInstance(Model):
    """Equipment Instance."""

    RELATED_NAME = 'equipment_instances'
    RELATED_QUERY_NAME = 'equipment_instance'

    equipment_general_type = \
        ForeignKey(
            to=EquipmentGeneralType,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=False,
            null=False,
            on_delete=PROTECT)

    equipment_unique_type = \
        ForeignKey(
            to=EquipmentUniqueType,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=True,
            null=True,
            on_delete=PROTECT)

    equipment_facility = \
        ForeignKey(
            to=EquipmentFacility,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=True,
            null=True,
            on_delete=PROTECT)

    name = \
        CharField(
            verbose_name='Equipment Instance',
            blank=False,
            null=False,
            unique=True,
            db_index=True,
            max_length=MAX_CHAR_LEN)

    info = \
        JSONField(
            blank=True,
            null=True)

    equipment_unique_type_groups = \
        ManyToManyField(
            to=EquipmentUniqueTypeGroup,
            related_name=RELATED_NAME,
            related_query_name=RELATED_QUERY_NAME,
            blank=True)

    class Meta:
        """Metadata."""

        verbose_name = 'Equipment Instance'
        verbose_name_plural = 'Equipment Instances'

        ordering = 'equipment_general_type', 'equipment_unique_type', 'name'

    def __str__(self):
        """Return string repr."""
        return (self.equipment_general_type.name.upper() +
                (f' UnqTp {self.equipment_unique_type.name}'
                 if self.equipment_unique_type else '') + f' #{self.name}')

    def save(self, *args, **kwargs):
        """Save."""
        self.name = clean_lower_str(self.name)

        if self.equipment_unique_type and (
                self.equipment_unique_type.equipment_general_type !=
                self.equipment_general_type):
            warnings.warn(
                message=(f'*** EQUIPMENT INSTANCE #{self.name}: '
                         f'EQUIPMENT UNIQUE TYPE {self.equipment_unique_type} '
                         'NOT OF EQUIPMENT GENERAL TYPE '
                         f'{self.equipment_general_type} ***'))

            self.equipment_unique_type = None

        super().save(*args, **kwargs)
Example #18
0
class Article(Publishable, AuthorMixin):
    parent = ForeignKey('Article',
                        related_name='article_parent',
                        blank=True,
                        null=True)

    headline = CharField(max_length=255)
    section = ForeignKey('Section')
    subsection = ForeignKey('Subsection',
                            related_name='article_subsection',
                            blank=True,
                            null=True)
    authors = ManyToManyField('Author', related_name='article_authors')
    topic = ForeignKey('Topic', null=True)
    tags = ManyToManyField('Tag')

    is_breaking = BooleanField(default=False)
    breaking_timeout = DateTimeField(blank=True, null=True)

    IMPORTANCE_CHOICES = [(i, i) for i in range(1, 6)]

    importance = PositiveIntegerField(validators=[MaxValueValidator(5)],
                                      choices=IMPORTANCE_CHOICES,
                                      default=3)

    READING_CHOICES = (
        ('anytime', 'Anytime'),
        ('morning', 'Morning'),
        ('midday', 'Midday'),
        ('evening', 'Evening'),
    )

    reading_time = CharField(max_length=100,
                             choices=READING_CHOICES,
                             default='anytime')

    class Meta:
        unique_together = (
            ('slug', 'head'),
            ('parent', 'slug', 'head'),
            ('parent', 'slug', 'is_published'),
        )

    AuthorModel = Author

    @property
    def title(self):
        return self.headline

    def get_related(self):
        return Article.objects.exclude(pk=self.id).filter(
            section=self.section,
            is_published=True).order_by('-published_at')[:5]

    def get_reading_list(self, ref=None, dur=None):
        articles = self.get_related()
        name = self.section.name

        return {
            'ids': ",".join([str(a.parent_id) for a in articles]),
            'name': name
        }

    def is_currently_breaking(self):
        if self.is_published and self.is_breaking:
            if self.breaking_timeout:
                return timezone.now() < self.breaking_timeout
        return False

    def save_tags(self, tag_ids):
        self.tags.clear()
        for tag_id in tag_ids:
            try:
                tag = Tag.objects.get(id=int(tag_id))
                self.tags.add(tag)
            except Tag.DoesNotExist:
                pass

    def save_topic(self, topic_id):
        if topic_id is None:
            self.topic = None
        else:
            try:
                topic = Topic.objects.get(id=int(topic_id))
                topic.update_timestamp()
                self.topic = topic
            except Topic.DoesNotExist:
                pass

    def get_absolute_url(self):
        """ Returns article URL. """
        return "%s%s/%s/" % (settings.BASE_URL, self.section.slug, self.slug)

    def get_subsection(self):
        """ Returns the subsection set in the parent article """
        return self.parent.subsection

    def save_subsection(self, subsection_id):
        """ Save the subsection to the parent article """
        Article.objects.filter(parent_id=self.parent.id).update(
            subsection_id=subsection_id)
Example #19
0
class ElementDeProgramme(CommonModel):
    evenement = ForeignKey('Evenement',
                           related_name='programme',
                           verbose_name=_('événement'),
                           on_delete=CASCADE)
    oeuvre = ForeignKey(
        'Oeuvre',
        related_name='elements_de_programme',
        verbose_name=_('œuvre'),
        blank=True,
        null=True,
        on_delete=PROTECT,
        help_text=_('Vous pouvez croiser le titre et le nom des auteurs. '
                    'Évitez les termes généraux comme « de », « la », « le », '
                    '« avec ».'))
    autre = CharField(_('autre'), max_length=500, blank=True)
    caracteristiques = ManyToManyField(CaracteristiqueDeProgramme,
                                       related_name='elements_de_programme',
                                       blank=True,
                                       verbose_name=_('caractéristiques'))
    NUMEROTATIONS = (
        ('O', _('Numéros')),  # O pour Ordered
        ('B', _('Numéros entre crochets (supposition)')),  # B pour Brackets
        ('U', _('Puce')),  # U pour Unordered
        ('E', _('Absente (entracte, etc)')),  # E pour Empty
    )
    numerotation = CharField(_('numérotation'),
                             choices=NUMEROTATIONS,
                             max_length=1,
                             default='O')
    NUMEROTATIONS_SANS_ORDRE = (
        'U',
        'E',
    )
    position = PositiveSmallIntegerField(_('position'), db_index=True)
    part_d_auteur = DecimalField(_('P. A.'),
                                 max_digits=6,
                                 decimal_places=2,
                                 blank=True,
                                 null=True)

    objects = ElementDeProgrammeManager()

    class Meta(object):
        verbose_name = _('élément de programme')
        verbose_name_plural = _('éléments de programme')
        ordering = ('position', )

    @staticmethod
    def invalidated_relations_when_saved(all_relations=False):
        if all_relations:
            return ('evenement', )
        return ()

    def calc_caracteristiques(self, tags=False):
        if self.pk is None:
            return ''
        return self.caracteristiques.html(tags=tags, caps=False)

    calc_caracteristiques.allow_tags = True
    calc_caracteristiques.short_description = _('caractéristiques')

    @property
    @model_method_cached()
    def numero(self):
        if hasattr(self, '_numero'):
            return self._numero
        if self.numerotation in self.NUMEROTATIONS_SANS_ORDRE:
            return ''
        return self.evenement.programme.exclude(
            Q(position__gt=self.position)
            | Q(numerotation__in=self.NUMEROTATIONS_SANS_ORDRE)).count()

    def save(self, *args, **kwargs):
        if self.position is None:
            n = self.evenement.programme.aggregate(n=Max('position'))['n']
            self.position = 0 if n is None else n + 1

        super(ElementDeProgramme, self).save(*args, **kwargs)

    @model_method_cached()
    def html(self, tags=True):
        has_pk = self.pk is not None

        distribution = ''
        add_distribution = False
        if has_pk:
            distribution = self.distribution.all()
            if distribution:
                distribution = distribution.html(tags=tags)
                add_distribution = True

        if self.oeuvre:
            out = self.oeuvre.html(tags)
        elif self.autre:
            out = self.autre
        elif distribution:
            out = distribution
            add_distribution = False
        else:
            warnings.warn(f'Il manque des champs '
                          f'dans <{self.__class__.__name__} pk={self.pk}>')
            return ''

        caracteristiques = self.calc_caracteristiques(tags=tags)
        if caracteristiques:
            out += f' [{caracteristiques}]'

        if add_distribution:
            out += f'. — {distribution}'

        return mark_safe(out)

    html.short_description = _('rendu HTML')
    html.allow_tags = True

    def __str__(self):
        return strip_tags(self.html(False))

    @staticmethod
    def autocomplete_search_fields():
        return (
            'oeuvre__prefixe_titre__unaccent__icontains',
            'oeuvre__titre__unaccent__icontains',
            'oeuvre__prefixe_titre_secondaire__unaccent__icontains',
            'oeuvre__titre_secondaire__unaccent__icontains',
            'oeuvre__genre__nom__unaccent__icontains',
        )
class Dummy(Model):
    name = CharField(max_length='100')
    moron = ForeignKey('Moron')
    idiots = ManyToManyField('Idiot')
Example #21
0
class Evenement(AutoriteModel):
    debut = AncrageSpatioTemporel(('date', ), verbose_name=_('début'))
    fin = AncrageSpatioTemporel(verbose_name=_('fin'))
    programme_incomplet = BooleanField(_('programme incomplet'), default=False)
    relache = BooleanField(_('relâche'), default=False, db_index=True)
    circonstance = CharField(_('circonstance'), max_length=500, blank=True)
    caracteristiques = ManyToManyField(CaracteristiqueDeProgramme,
                                       related_name='evenements',
                                       blank=True,
                                       verbose_name=_('caractéristiques'))

    recette_generale = DecimalField(_('recette générale'),
                                    max_digits=7,
                                    decimal_places=2,
                                    blank=True,
                                    null=True)
    recette_par_billets = CharField(
        _('recette par titre de billets'),
        max_length=30,
        validators=[plus_separated_integers_validator],
        blank=True)

    objects = EvenementManager()

    class Meta(object):
        verbose_name = _('événement')
        verbose_name_plural = _('événements')
        ordering = ('debut_date', 'debut_heure', 'debut_lieu',
                    'debut_lieu_approx')
        permissions = (('can_change_status', _('Peut changer l’état')), )

    @staticmethod
    def invalidated_relations_when_saved(all_relations=False):
        if all_relations:
            return ('dossiers', )
        return ()

    @permalink
    def get_absolute_url(self):
        return 'evenement_pk', (self.pk, )

    def permalien(self):
        return self.get_absolute_url()

    def link(self):
        return href(self.get_absolute_url(), force_text(self))

    link.short_description = _('lien')
    link.allow_tags = True

    def calc_caracteristiques(self, tags=True, caps=True):
        if self.pk is None:
            return ''
        return self.caracteristiques.html(tags=tags, caps=caps)

    def get_meta_name(self, tags=False):
        if self.circonstance:
            out = self.circonstance
        else:
            distribution = self.distribution.all()
            if distribution:
                out = distribution.html(tags=tags)
            else:
                programme = self.programme.all()
                if programme.exists():
                    element = programme[0]
                    out = element.oeuvre or element.autre
                else:
                    return ''
        return microdata(out, 'summary', tags=tags)

    def html(self, tags=True):
        relache = ''
        circonstance = ''
        if self.circonstance:
            circonstance = hlp(self.circonstance, ugettext('circonstance'),
                               tags)
        if self.relache:
            relache = microdata(ugettext('Relâche'), 'eventType', tags=tags)

        lieu = microdata(self.debut.lieu_str(tags), 'location', tags=tags)

        return mark_safe(
            str_list((lieu, circonstance, self.debut.heure_str(), relache)))

    html.short_description = _('rendu HTML')
    html.allow_tags = True

    def has_program(self):
        if self.relache:
            return True
        if hasattr(self, '_has_program'):
            return self._has_program
        return self.programme.exists()

    has_program.short_description = _('programme')
    has_program.boolean = True

    def has_source(self):
        if hasattr(self, '_has_source'):
            return self._has_source
        return self.sources.exists()

    has_source.short_description = _('source')
    has_source.boolean = True
    has_source.admin_order_field = 'sources'

    @property
    def oeuvres(self):
        return apps.get_model(
            'libretto',
            'Oeuvre').objects.filter(elements_de_programme__evenement=self)

    def get_saisons(self):
        # TODO: Gérer les lieux de fin.
        qs = apps.get_model('libretto', 'Saison').objects.filter(
            debut__lte=self.debut_date, fin__gte=self.debut_date)
        extra_where = """
            ensemble_id IN ((
                SELECT ensemble_id
                FROM libretto_elementdedistribution
                WHERE evenement_id = %s
            ) UNION (
                SELECT distribution.ensemble_id
                FROM libretto_elementdeprogramme AS programme
                INNER JOIN libretto_elementdedistribution AS distribution ON (distribution.element_de_programme_id = programme.id)
                WHERE programme.evenement_id = %s))"""
        extra_params = [self.pk, self.pk]
        if self.debut_lieu_id is not None:
            extra_where += ' OR lieu_id = %s'
            extra_params.append(self.debut_lieu_id)

        return qs.extra(where=(extra_where, ), params=extra_params)

    def clean(self):
        if self.fin_lieu is not None and self.debut_lieu is None:
            raise ValidationError(
                _('Le lieu de fin est rempli sans lieu de début. '
                  'Merci de retirer le lieu de fin '
                  'ou remplir le lieu de début.'))

    def __str__(self):
        out = self.debut.date_str(False)
        out = capfirst(out)
        out += f'\u00A0> {self.html(False)}'
        return strip_tags(out)

    def related_label(self):
        return href(reverse('admin:libretto_evenement_change',
                            args=(self.pk, )),
                    force_text(self),
                    new_tab=True)

    @staticmethod
    def autocomplete_search_fields():
        return (
            'circonstance__unaccent__icontains',
            'debut_lieu__nom__unaccent__icontains',
            'debut_lieu__parent__nom__unaccent__icontains',
            'debut_date__icontains',
            'debut_heure__icontains',
            'debut_lieu_approx__unaccent__icontains',
            'debut_date_approx__unaccent__icontains',
            'debut_heure_approx__unaccent__icontains',
        )
Example #22
0
class App(TranslatableModel):
    objects = AppManager()
    id = CharField(max_length=256,
                   unique=True,
                   primary_key=True,
                   verbose_name=_('ID'),
                   help_text=_('app ID, identical to folder name'))
    categories = ManyToManyField('Category', verbose_name=_('Category'))
    translations = TranslatedFields(
        name=CharField(max_length=256,
                       verbose_name=_('Name'),
                       help_text=_('Rendered app name for users')),
        summary=CharField(
            max_length=256,
            verbose_name=_('Summary'),
            help_text=_('Short text describing the app\'s purpose')),
        description=TextField(verbose_name=_('Description'),
                              help_text=_('Will be rendered as Markdown')))
    # resources
    user_docs = URLField(max_length=256,
                         blank=True,
                         verbose_name=_('User documentation URL'))
    admin_docs = URLField(max_length=256,
                          blank=True,
                          verbose_name=_('Admin documentation URL'))
    developer_docs = URLField(max_length=256,
                              blank=True,
                              verbose_name=_('Developer documentation URL'))
    issue_tracker = URLField(max_length=256,
                             blank=True,
                             verbose_name=_('Issue tracker URL'))
    website = URLField(max_length=256, blank=True, verbose_name=_('Homepage'))
    discussion = URLField(max_length=256, blank=True, verbose_name=_('Forum'))
    created = DateTimeField(auto_now_add=True,
                            editable=False,
                            verbose_name=_('Created at'))
    last_modified = DateTimeField(auto_now=True,
                                  editable=False,
                                  db_index=True,
                                  verbose_name=_('Updated at'))
    owner = ForeignKey(settings.AUTH_USER_MODEL,
                       verbose_name=_('App owner'),
                       on_delete=CASCADE,
                       related_name='owned_apps')
    co_maintainers = ManyToManyField(settings.AUTH_USER_MODEL,
                                     blank=True,
                                     verbose_name=_('Co-Maintainers'),
                                     related_name='co_maintained_apps')
    authors = ManyToManyField('AppAuthor',
                              blank=True,
                              related_name='apps',
                              verbose_name=_('App authors'))
    is_featured = BooleanField(verbose_name=_('Featured'), default=False)
    rating_recent = FloatField(verbose_name=_('Recent rating'), default=0.5)
    rating_overall = FloatField(verbose_name=_('Overall rating'), default=0.5)
    rating_num_recent = IntegerField(
        verbose_name=_('Number of recently submitted ratings'), default=0)
    rating_num_overall = IntegerField(
        verbose_name=_('Number of overall submitted ratings'), default=0)
    last_release = DateTimeField(editable=False,
                                 db_index=True,
                                 verbose_name=_('Last release at'),
                                 default=timezone.now)
    certificate = TextField(verbose_name=_('Certificate'))
    ownership_transfer_enabled = BooleanField(
        verbose_name=_('Ownership transfer enabled'),
        default=False,
        help_text=_('If enabled, a user can try to register the same app '
                    'again using the public certificate and signature. If he '
                    'does, the app will be transferred to him.'))
    is_integration = BooleanField(verbose_name=_('Integration (i.e. Outlook '
                                                 'plugin)'),
                                  default=False)
    approved = BooleanField(verbose_name=_('Used to approve integrations'),
                            default=False)

    class Meta:
        verbose_name = _('App')
        verbose_name_plural = _('Apps')

    def __str__(self) -> str:
        return self.name

    def can_update(self, user: User) -> bool:
        return self.owner == user or user in self.co_maintainers.all()

    def can_delete(self, user: User) -> bool:
        return self.owner == user

    @property
    def discussion_url(self):
        if self.discussion:
            return self.discussion
        else:
            return '%s/c/apps/%s' % (settings.DISCOURSE_URL,
                                     self.id.replace('_', '-'))

    def _get_grouped_releases(self, get_release_func):
        releases = NextcloudRelease.objects.all()
        versions = map(lambda r: r.version, releases)
        compatible_releases = map(lambda v: (v, get_release_func(v)), versions)
        grouped_releases = group_by_main_version(dict(compatible_releases))
        # deduplicate releases
        result = {}
        for version, releases in grouped_releases.items():
            result[version] = list(distinct(releases, lambda r: r.version))
        return result

    def releases_by_platform_v(self):
        """Looks up all compatible stable releases for each platform
        version.

        Example of returned dict:

        {'9.1': [<AppRelease object>, <AppRelease object>],
        '9.0': [<AppRelease object>]}

        :return dict with all compatible stable releases for each platform
                version.
        """
        return self._get_grouped_releases(self.compatible_releases)

    def unstable_releases_by_platform_v(self):
        """Looks up all compatible unstable releases for each platform version.

        Example of returned dict:

        {'9.1': [<AppRelease object>, <AppRelease object>],
        '9.0': [<AppRelease object>]}

        :return dict with all compatible unstable releases for each platform
                version.
        """
        return self._get_grouped_releases(self.compatible_unstable_releases)

    def latest_releases_by_platform_v(self):
        """Looks up the latest stable and unstable release for each platform
        version.

        Example of returned dict:

        {'9.1': {
            'stable': <AppRelease object>,
            'unstable': <AppRelease object>
        },
        '9.0': {
            'stable': <AppRelease object>
        }}

        :return dict with the latest stable and unstable release for each
                platform version.
        """
        stable = self.releases_by_platform_v()
        unstable = self.unstable_releases_by_platform_v()

        def filter_latest(pair):
            version, releases = pair
            return (version, self._latest(releases))

        latest_stable = dict(map(filter_latest, stable.items()))
        latest_unstable = dict(map(filter_latest, unstable.items()))
        all_versions = set(chain(latest_stable.keys(), latest_unstable.keys()))

        def stable_or_unstable_releases(ver):
            return (ver, {
                'stable': latest_stable.get(ver, None),
                'unstable': latest_unstable.get(ver, None)
            })

        return dict(map(stable_or_unstable_releases, all_versions))

    def compatible_releases(self, platform_version, inclusive=True):
        """Returns all stable releases of this app that are compatible
        with the given platform version.

        :param inclusive: Use inclusive version check (see
                          AppRelease.is_compatible()).
        :return a sorted list of all compatible stable releases.
        """

        return sorted(
            filter(
                lambda r: r.is_compatible(platform_version, inclusive) and
                not r.is_unstable, self.releases.all()),
            key=lambda r: AppSemVer(r.version, r.is_nightly, r.last_modified),
            reverse=True)

    def compatible_unstable_releases(self, platform_version, inclusive=True):
        """Returns all unstable releases of this app that are compatible with
        the given platform version.

        :param inclusive: Use inclusive version check (see
                          AppRelease.is_compatible()).
        :return a sorted list of all compatible unstable releases.
        """

        return sorted(
            filter(
                lambda r: r.is_compatible(platform_version, inclusive) and r.
                is_unstable, self.releases.all()),
            key=lambda r: AppSemVer(r.version, r.is_nightly, r.last_modified),
            reverse=True)

    def _latest(self, releases):
        try:
            return max(releases,
                       key=lambda r: AppSemVer(r.version, r.is_nightly, r.
                                               last_modified))
        except ValueError:
            return None

    def save(self, *args, **kwargs):
        # If the certificate has changed, delete all releases.
        try:
            if self.pk is not None:
                orig = App.objects.get(pk=self.pk)
                current = self.certificate.replace('\r', '').strip()
                former = orig.certificate.replace('\r', '').strip()
                # for some reason the django admin inserts \r\n for \n so
                # saving a model in the admin with the same cert kills all
                # releases
                if current != former:
                    self.releases.all().delete()
        except self.DoesNotExist:
            pass
        super().save(*args, **kwargs)
Example #23
0
class TBaz(Model):
    foo = ManyToManyField(TFoo)

    class Meta:
        ordering = ('pk', )
Example #24
0
class DNSResource(CleanSave, TimestampedModel):
    """A `DNSResource`.

    :ivar name: The leftmost label for the resource. (No dots.)
    :ivar domain: Which (forward) DNS zone does this resource go in.
    :ivar ip_addresses: many-to-many linkage to `StaticIPAddress`.
    :ivar objects: An instance of the class :class:`DNSResourceManager`.
    """

    class Meta(DefaultMeta):
        """Needed for South to recognize this model."""

        verbose_name = "DNSResource"
        verbose_name_plural = "DNSResources"

    objects = DNSResourceManager()

    # If name is blank or None, then we'll use $IFACE.$NODENAME.$DOMAIN (and
    # $NODENAME.$DOMAIN if this is the pxeboot interface), otherwise we'll use
    # only NAME.$DOMAIN.
    # There can be more than one name=None entry, so unique needs to be False.
    # We detect and reject duplicates in clean()
    # This could be as many as 3 dot-separated labels, because of SRV records.
    name = CharField(
        max_length=191, editable=True, null=True, blank=True, unique=False
    )

    # Different resource types can have different TTL values, though all of the
    # records of a given RRType for a given FQDN "must" have the same TTL.
    # If the DNS zone file has different TTLs for the same RRType on a label,
    # then BIND uses the first one in the file, and logs something similar to:
    #   /etc/bind/maas/zone.maas:25: TTL set to prior TTL (10)
    # We allow this condition to happen so that the user has a hope of changing
    # TTLs for a multi-entry RRset.

    # TTL for any ip_addresses:  non-address TTLs come from DNSData
    address_ttl = PositiveIntegerField(default=None, null=True, blank=True)

    domain = ForeignKey(
        Domain, default=get_default_domain, editable=True, on_delete=PROTECT
    )

    ip_addresses = ManyToManyField(
        "StaticIPAddress", editable=True, blank=True
    )

    # DNSData model has non-ipaddress entries.

    def __unicode__(self):
        return "name=%s" % self.get_name()

    def __str__(self):
        return "name=%s" % self.get_name()

    @property
    def fqdn(self):
        """Fully qualified domain name for this DNSResource.

        Return the FQDN for this DNSResource.
        """
        if self.name == "@":
            return self.domain.name
        else:
            return "%s.%s" % (self.name, self.domain.name)

    def has_static_ip(self):
        return self.ip_addresses.exclude(
            alloc_type=IPADDRESS_TYPE.DISCOVERED
        ).exists()

    def get_addresses(self):
        """Return all addresses associated with this FQDN."""
        # Since Node.hostname is unique, this will be at most 1 node.
        node = Node.objects.filter(
            hostname=self.name, domain_id=self.domain_id
        )
        ips = [ip.get_ip() for ip in self.ip_addresses.all()]
        if node.exists():
            ips += node[0].static_ip_addresses()
        return ips

    def get_name(self):
        """Return the name of the dnsresource."""
        return self.name

    def clean(self, *args, **kwargs):
        # Avoid recursive imports.
        from maasserver.models.dnsdata import DNSData

        # make sure that we have a domain
        if self.domain is None or self.domain == "":
            self.domain = Domain.objects.get_default_domain()
        # if we have a name, make sure that it is unique in our dns zone.
        if self.id is None and self.name is not None and self.name != "":
            rrset = DNSResource.objects.filter(
                name=self.name, domain=self.domain
            )
            if rrset.count() > 0:
                raise ValidationError(
                    "Labels must be unique within their zone."
                )
        # If we have an ip addresses, then we need to have a valid name.
        # TXT records don't require that we have much at all.
        if self.id is not None and self.ip_addresses.count() > 0:
            validate_dnsresource_name(self.name, "A")
            # This path could be followed if the user is adding a USER_RESERVED
            # ip address, where the FQDN already has a CNAME assigned to it.
            # Node.fqdn takes a different path, and should win when it comes to
            # DNS generation.
            num_cname = DNSData.objects.filter(
                dnsresource_id=self.id, rrtype="CNAME"
            ).count()
            if num_cname > 0:
                raise ValidationError("Cannot add address: CNAME present.")
        super().clean(*args, **kwargs)

    def render_json(self, system_id):
        """Render json.  System_id is the system_id for the node, if one
        exists.  Addresses are rendered in the calling function."""
        return sorted(
            [
                {
                    "hostname": self.name,
                    "ttl": data.ttl,
                    "rrtype": data.rrtype,
                    "rrdata": data.rrdata,
                    "system_id": system_id,
                }
                for data in self.dnsdata_set.all()
            ]
        )
Example #25
0
class FieldFromModelManyToManyTest(Model):
    foo_many_to_many = ManyToManyField(Foo)
Example #26
0
class TestChild(TestParent):
    public = BooleanField(default=False)
    permissions = ManyToManyField('auth.Permission', blank=True)
Example #27
0
class Project(models.Model):
    """
    The make_cdc_flu_contests_project_app class representing a forecast challenge, including metadata, core data,
    targets, and model entries.
    """

    # w/out related_name we get: forecast_app.Project.model_owners:
    #   (fields.E304) Reverse accessor for 'Project.model_owners' clashes with reverse accessor for 'Project.owner'.
    owner = models.ForeignKey(User,
                              related_name='project_owner',
                              on_delete=models.SET_NULL,
                              blank=True,
                              null=True,
                              help_text="The project's owner.")

    is_public = models.BooleanField(
        default=True,
        help_text=
        "Controls project visibility. False means the project is private and "
        "can only be accessed by the project's owner or any of its model_owners. "
        "True means it is publicly accessible.")

    model_owners = ManyToManyField(
        User,
        blank=True,
        help_text=
        "Users who are allowed to create, edit, and delete ForecastModels "
        "in this project. Or: non-editing users who simply need access "
        "to a private project. Use control/command click to add/remove from "
        "the list. ")

    name = models.TextField()

    WEEK_TIME_INTERVAL_TYPE = 'w'
    BIWEEK_TIME_INTERVAL_TYPE = 'b'
    MONTH_TIME_INTERVAL_TYPE = 'm'
    TIME_INTERVAL_TYPE_CHOICES = ((WEEK_TIME_INTERVAL_TYPE, 'Week'),
                                  (BIWEEK_TIME_INTERVAL_TYPE, 'Biweek'),
                                  (MONTH_TIME_INTERVAL_TYPE, 'Month'))
    time_interval_type = models.CharField(
        max_length=1,
        choices=TIME_INTERVAL_TYPE_CHOICES,
        default=WEEK_TIME_INTERVAL_TYPE,
        help_text="Used when visualizing the x axis label.")
    visualization_y_label = models.TextField(
        help_text="Used when visualizing the Y axis label.")
    description = models.TextField(
        help_text=
        "A few paragraphs describing the project. Please see documentation for"
        "what should be included here - 'real-time-ness', time_zeros, etc.")
    home_url = models.URLField(help_text="The project's home site.")
    logo_url = models.URLField(blank=True,
                               null=True,
                               help_text="The project's optional logo image.")
    core_data = models.URLField(
        help_text=
        "Directory or Zip file containing data files (e.g., CSV files) made made available to everyone in "
        "the challenge, including supplemental data like Google queries or weather."
    )

    def __repr__(self):
        return str((self.pk, self.name))

    def __str__(self):  # todo
        return basic_str(self)

    def save(self, *args, **kwargs):
        """
        Validates my TimeZero.timezero_dates for uniqueness.
        """
        found_timezero_dates = []
        for timezero in self.timezeros.all():
            if timezero.timezero_date not in found_timezero_dates:
                found_timezero_dates.append(timezero.timezero_date)
            else:
                raise ValidationError(
                    "found duplicate TimeZero.timezero_date: {}".format(
                        timezero.timezero_date))

        # done
        super().save(*args, **kwargs)

    def time_interval_type_as_str(self):
        """
        :return: my time_interval_type as a human-friendly string from TIME_INTERVAL_TYPE_CHOICES
        """
        for db_value, human_readable_value in Project.TIME_INTERVAL_TYPE_CHOICES:
            if db_value == self.time_interval_type:
                return human_readable_value

    def get_absolute_url(self):
        return reverse('project-detail', args=[str(self.pk)])

    def get_class(self):
        """
        :return: view utility that simply returns a my class as a string. used by delete_modal_snippet.html
        """
        return self.__class__.__name__

    def html_id(self):
        """
        :return: view utility that returns a unique HTML id for this object. used by delete_modal_snippet.html
        """
        return self.__class__.__name__ + '_' + str(self.pk)

    #
    # season-related utilities
    #

    def seasons(self):
        """
        :return: list of season names for this project based on my timezeros
        """
        return list(
            self.timezeros.filter(
                is_season_start=True).order_by('timezero_date').values_list(
                    'season_name', flat=True))

    def timezeros_in_season(self, season_name):
        """
        Utility that returns a sorted list of TimeZeros for season_name.

        :param season_name: a valid season name (see seasons()) or None, which is used to access TimeZeros that have
            no season. For the latter, there are two cases:
            1) there are no seasons at all
            2) there are seasons, but the first starts after the first TimeZero, i.e., my TimeZeros start with some
               non-season ones that are followed by some seasons
        :return: two cases based on whether season_name is None. 1) If not None: returns a list of TimeZeros that are
            within season_name, i.e., those that start with the TimeZero named season_name and go TO the next season,
            or to the end if season_name is the last season. 2) If None: returns based on the two cases listed above
            for season_name: 1) no seasons at all: return all TimeZeros. 2) starts with some non-seasons: return those
            up TO the first season.
        """
        # start with all TimeZeros - case #1 (no seasons at all), and filter as needed
        season_timezeros_qs = self.timezeros.all()
        if season_name:
            season_tz = season_timezeros_qs.filter(
                season_name=season_name).first()
            if not season_tz:
                raise RuntimeError(
                    "invalid season_name. season_name={}, seasons={}".format(
                        season_name, self.seasons()))

            season_timezeros_qs = season_timezeros_qs.filter(
                timezero_date__gte=season_tz.timezero_date)
            next_season_tz = season_timezeros_qs \
                .filter(is_season_start=True,
                        timezero_date__gt=season_tz.timezero_date) \
                .first()
            if next_season_tz:
                season_timezeros_qs = season_timezeros_qs.filter(
                    timezero_date__lt=next_season_tz.timezero_date)
        else:  # no season_name
            first_season_tz = season_timezeros_qs.filter(
                is_season_start=True).first()
            if first_season_tz:  # case #2 (seasons after initial TZs)
                season_timezeros_qs = season_timezeros_qs.filter(
                    timezero_date__lt=first_season_tz.timezero_date)
        return list(season_timezeros_qs.order_by('timezero_date'))

    def start_end_dates_for_season(self, season_name):
        """
        :param season_name: same as timezeros_in_season() - can be None
        :return: 2-tuple: (start_date, end_date) for season_name. this is a closed interval - both are included.
            Note that start_date == end_date if there is only one TimeZero. returns None if no TimeZeros found
        """
        timezeros = self.timezeros_in_season(season_name)
        if len(timezeros) == 0:
            return None

        return timezeros[0].timezero_date, timezeros[-1].timezero_date

    def season_name_containing_timezero(self, timezero, timezeros=None):
        """
        :return: season_name of the season that contains timezero, or None if it's not in a season. timezeros, if
            passed, allows optimizing by callers who compute timezeros only once.
        """
        timezeros = timezeros or self.timezeros.all()
        if timezero not in timezeros:
            raise RuntimeError(
                "TimeZero not found in timezeros: timezero={}, timezeros={}".
                format(timezero, timezeros))

        # order my timezeros by date and then iterate from earliest to latest, keeping track of the current season and
        # returning the first match. must handle two cases: the earliest timezero defines a season, or not
        containing_season_name = None  # return value. updated in loop
        for project_timezero in timezeros.order_by('timezero_date'):
            if project_timezero.is_season_start:
                containing_season_name = project_timezero.season_name
            if project_timezero == timezero:
                return containing_season_name

    def timezero_to_season_name(self):
        """
        :return: a dict mapping each of my timezeros -> containing season name
        """
        _timezero_to_season_name = {}
        containing_season_name = None
        for timezero in self.timezeros.order_by('timezero_date'):
            if timezero.is_season_start:
                containing_season_name = timezero.season_name
            _timezero_to_season_name[timezero] = containing_season_name
        return _timezero_to_season_name

    #
    # time-related utilities
    #

    def forecasts_for_timezero(self, timezero):
        """
        :param timezero: a TimeZero
        :return: a list of Forecasts for timezero for each of my models
        """
        return [
            forecast_model.forecast_for_time_zero(timezero)
            for forecast_model in self.models.all()
        ]

    def time_zero_for_timezero_date(self, timezero_date):
        """
        :return: the first TimeZero in me that has a timezero_date matching timezero_date
        """
        return self.timezeros.filter(timezero_date=timezero_date).first()

    def time_interval_type_to_foresight(self):
        """
        :return: my time_interval_type formatted for D3-Foresight's pointType
        """
        return dict(Project.TIME_INTERVAL_TYPE_CHOICES)[
            self.time_interval_type].lower()

    def last_update(self):
        """
        Returns the datetime.datetime of the last time this project was "updated": the latest Forecast's created_at.
        Returns None if no forecasts.
        """
        from .forecast import Forecast  # avoid circular imports

        latest_forecast = Forecast.objects.filter(
            forecast_model__project=self).order_by('-created_at').first()
        return latest_forecast.created_at if latest_forecast else None

    #
    # count-related functions
    #

    def num_models_forecasts(self):
        """
        :return: a 2-tuple: (num_models, num_forecasts)
        """
        from .forecast import Forecast  # avoid circular imports

        num_models = self.models.filter(project=self, is_oracle=False).count()
        num_forecasts = Forecast.objects.filter(
            forecast_model__project=self,
            forecast_model__is_oracle=False).count()
        return num_models, num_forecasts

    def num_pred_ele_rows_all_models(self, is_oracle=True):
        """
        :return: the total number of PredictionElements across all my models' forecasts, for all types of Predictions.
            can be very slow for large databases
        """
        from forecast_app.models import PredictionElement  # avoid circular imports

        return PredictionElement.objects.filter(
            forecast__forecast_model__project=self,
            forecast__forecast_model__is_oracle=is_oracle).count()

    #
    # visualization-related functions
    #

    def step_ahead_targets(self):
        return self.targets.filter(is_step_ahead=True) \
            .order_by('name')

    def numeric_targets(self):
        """
        :return: a list of Targets whose values are numeric - either int or float. used by scoring
        """
        from forecast_app.models import Target  # avoid circular imports


        return self.targets.filter(type__in=[Target.CONTINUOUS_TARGET_TYPE, Target.DISCRETE_TARGET_TYPE]) \
            .order_by('name')
Example #28
0
class Competition(Model):
    def __str__(self):
        s = self.slug
        s += ": " + ", ".join([i.slug for i in self.questionsets.all()])
        return s

    def get_absolute_url(self):
        return reverse('competition_detail', kwargs={'slug': str(self.slug)})

    title = CharField(max_length=256,
                      null=True,
                      blank=True,
                      verbose_name=_("title"))
    promoted = BooleanField(default=False, verbose_name=_("promoted"))
    public = BooleanField(
        default=True,
        verbose_name=_("public competition"),
        help_text=_(
            "Public competitions are listed on the competiton list page"))
    slug = SlugField(unique=True, verbose_name=_("slug"))
    administrator_code_generator = ForeignKey(
        CodeGenerator,
        related_name='administrator_code_competition_set',
        verbose_name=_("administrator code generator"),
        on_delete=CASCADE)
    competitor_code_generator = ForeignKey(
        CodeGenerator,
        related_name='competitor_code_competition_set',
        verbose_name=_("competitor code generator"),
        on_delete=CASCADE)
    questionsets = ManyToManyField('QuestionSet',
                                   through='CompetitionQuestionSet',
                                   verbose_name=_("Question sets"))
    start = DateTimeField(verbose_name=_("start"))
    # duration in seconds
    duration = IntegerField(
        default=45 * 60,  # 60s * 45 = 1h.
        verbose_name=_("duration"),
        help_text=_("Duration of the competition in seconds"))
    end = DateTimeField(verbose_name=_("end"))
    motd = TextField(blank=True, verbose_name=_("message of the day"))

    @property
    def is_over(self):
        """
        Return true when competition is finished.
        """
        return self.end < timezone.now()

    @property
    def guests_allowed(self):
        return self.competitionquestionset_set.filter(
            guest_code__isnull=False).exists()

    @staticmethod
    def ongoing_competitions():
        """
        Return a list of ongoing competitions.
        Complexity: linearly dependent on the number of all competitions.
        """
        now = timezone.now()
        return Competition.objects.filter(start__lte=now, end__gte=now)

    @property
    def is_ongoing(self):
        return self.start <= timezone.now() <= self.end

    @classmethod
    def get_cached_by_slug(cls, slug):
        c = cache.get('competition_by_slug__' + slug, None)
        if c is None:
            c = cls.objects.select_related(
                'administrator_code_generator',
                'competitor_code_generator',
                'administrator_code_generator__format',
                'competitor_code_generator__format',
            ).get(slug=slug)
            print("  adding", slug, "to cache")
            cache.set('competition_by_slug__' + slug, c)
        return c

    def expand_competitor_code(self, short_code, competition_questionset):
        sep = self.competitor_code_generator.format.separator
        return competition_questionset.slug_str() + sep + short_code

    def split_competitor_code(self, access_code):
        sep = self.competitor_code_generator.format.separator
        return access_code.split(sep)

    def grade_answers(self,
                      grader_runtime_manager=None,
                      update_graded=False,
                      regrade=False):
        grader_runtime_manager = graders.init_runtimes(grader_runtime_manager)
        if grader_runtime_manager is None:
            grader_runtime_manager = graders.RuntimeManager()
            grader_runtime_manager.start_runtimes()
        if update_graded:
            self.update_graded_answers(
                regrade=regrade, grader_runtime_manager=grader_runtime_manager)
            if regrade:
                return
        for cq in CompetitionQuestionSet.objects.filter(competition=self):
            cq.grade_answers(grader_runtime_manager=grader_runtime_manager,
                             update_graded=False,
                             regrade=regrade)

    def update_graded_answers(self,
                              regrade=False,
                              grader_runtime_manager=None):
        grader_runtime_manager = graders.init_runtimes(grader_runtime_manager)
        for cq in CompetitionQuestionSet.objects.filter(competition=self):
            cq.update_graded_answers(
                regrade=regrade, grader_runtime_manager=grader_runtime_manager)

    def admin_privilege_choices(self, access_code):
        return filter(
            lambda x: self.administrator_code_generator.code_matches(
                access_code, {'admin_privileges': [x[0]]}), ADMIN_PRIVILEGES)

    def allowed_effect_choices(self, access_code):
        return filter(
            lambda x: self.administrator_code_generator.code_matches(
                access_code, {'allowed_effects': [x[0]]}), CODE_EFFECTS)

    def competitor_privilege_choices(self, access_code):
        return filter(
            lambda x: self.administrator_code_generator.code_matches(
                access_code, {'competitor_privileges': [x[0]]}),
            COMPETITOR_PRIVILEGES)

    def max_admin_code_data(self, access_code):
        return {
            'admin_privileges':
            [i[0] for i in self.admin_privilege_choices(access_code)],
            'allowed_effects':
            [i[0] for i in self.allowed_effect_choices(access_code)],
            'competitor_privileges':
            [i[0] for i in self.competitor_privilege_choices(access_code)]
        }

    def max_competitor_code_data(self, access_code):
        return {
            'competitor_privileges':
            [i[0] for i in self.competitor_privilege_choices(access_code)]
        }

    def competitor_code_create(self,
                               access_code,
                               competition_questionset=None,
                               code_data=None):
        if code_data is None:
            code_data = self.max_competitor_code_data(access_code)
        if competition_questionset is not None:
            code_data['competition_questionset'] = [
                competition_questionset.slug_str()
            ]
        c = self.competitor_code_generator.create_code(code_data)
        c.save()
        return c

    def master_code_create(self):
        c = self.administrator_code_generator.create_code({
            'admin_privileges': [i[0] for i in ADMIN_PRIVILEGES],
            'competitor_privileges': [i[0] for i in COMPETITOR_PRIVILEGES],
            'allowed_effects': [i[0] for i in CODE_EFFECTS],
        })
        c.save()
        return c

    def admin_code_create(self, access_code, code_data=None):
        if code_data is None:
            code_data = self.max_admin_code_data(access_code)
        c = self.administrator_code_generator.create_code(code_data)
        c.save()
        return c
Example #29
0
class AbstractDocument(CremeEntity):
    title = CharField(_(u'Name'), max_length=100)
    description = TextField(_(u'Description'),
                            blank=True).set_tags(optional=True)
    filedata = FileField(_(u'File'),
                         max_length=500,
                         upload_to='upload/documents')
    linked_folder = ForeignKey(
        settings.DOCUMENTS_FOLDER_MODEL,
        verbose_name=_(u'Folder'),
        on_delete=PROTECT,
    )
    mime_type = ForeignKey(
        MimeType,
        verbose_name=_(u'MIME type'),
        editable=False,
        on_delete=PROTECT,
        null=True,
    )
    categories = ManyToManyField(
        DocumentCategory,
        verbose_name=_(u'Categories'),
        # related_name='+',
        blank=True,
    ).set_tags(optional=True)

    creation_label = _(u'Create a document')
    save_label = _(u'Save the document')

    class Meta:
        abstract = True
        manager_inheritance_from_future = True
        app_label = 'documents'
        verbose_name = _(u'Document')
        verbose_name_plural = _(u'Documents')
        ordering = ('title', )

    def __str__(self):
        return u'{} - {}'.format(self.linked_folder, self.title)

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

    @staticmethod
    def get_create_absolute_url():
        return reverse('documents__create_document')

    def get_edit_absolute_url(self):
        return reverse('documents__edit_document', args=(self.id, ))

    @staticmethod
    def get_lv_absolute_url():
        return reverse('documents__list_documents')

    @staticmethod
    def get_linkeddoc_relations(entity):
        return Relation.objects.filter(subject_entity=entity.id,
                                       type=REL_SUB_RELATED_2_DOC)

    def get_dl_url(self):
        import os

        return settings.MEDIA_URL + str(self.filedata).replace(os.sep, '/')

    def get_entity_summary(self, user):
        if not user.has_perm_to_view(self):
            return self.allowed_str(user)

        if self.mime_type.is_image:
            return format_html(
                u'<img class="entity-summary" src="{url}" alt="{name}" title="{name}"/>',
                url=self.get_dl_url(),
                name=self.title,
            )

        # return super(AbstractDocument, self).get_entity_summary(user)
        return super().get_entity_summary(user)

    def save(self, *args, **kwargs):
        if not self.pk:  # Creation
            mime_name = guess_type(self.filedata.name)[0]

            if mime_name is not None:
                self.mime_type = MimeType.objects.get_or_create(
                    name=mime_name)[0]

        # super(AbstractDocument, self).save(*args, **kwargs)
        super().save(*args, **kwargs)
Example #30
0
class Study(Model):

    EEA = 'eea'
    OTHER = 'other'
    REQUESTED_BY_CHOICES = (
        (EEA, 'EEA'),
        (OTHER, 'Other'),
    )

    YES = 1
    NO = 0
    YES_NO_CHOICES = (
        ('', '----'),
        (YES, 'Yes'),
        (NO, 'No'),
    )

    POLICY = 'policy'
    NON_POLICY = 'non_policy'
    PURPOSE_CHOICES = (
        (POLICY, 'Support to policy'),
        (NON_POLICY, 'Non-policy (research, civil initiative, NGOs...'),
    )

    ACTIVITY = 'activity'
    EVALUATION = 'evaluation'
    TYPE_CHOICES = (
        (ACTIVITY, 'Forward looking activity'),
        (EVALUATION, 'Evaluation'),
    )

    BLOSSOM_CHOICES = (
        (YES, 'BLOSSOM study'),
        (NO, 'other study'),
    )

    draft = BooleanField(default=True)

    created_on = DateTimeField(auto_now_add=True)

    last_updated = DateTimeField(auto_now_add=True, auto_now=True)

    user_id = CharField(max_length=64, blank=True)

    title = CharField('title in English', max_length=255)

    languages = ManyToManyField('Language',
                                verbose_name='language of the study',
                                through='StudyLanguage')

    url = URLField(blank=True)

    study_type = CharField(
        'I want to add a new',
        choices=TYPE_CHOICES,
        max_length=128,
        null=True,
    )

    blossom = IntegerField(
        'Approach to evaluation',
        choices=BLOSSOM_CHOICES,
        default=NO,
    )

    requested_by = CharField('who requested the study?',
                             max_length=64,
                             choices=REQUESTED_BY_CHOICES,
                             blank=True)

    start_date = DateField('start date', null=True, blank=True)

    end_date = DateField('end date')

    lead_author = TextField('lead author')

    other = TextField('other organisations/authors or contact persons',
                      blank=True)

    purpose_and_target = CharField(
        'purpose and target audience',
        max_length=128,
        choices=PURPOSE_CHOICES,
        blank=True,
    )

    additional_information = TextField('additional information', blank=True)

    phase_of_policy = ForeignKey('PhasesOfPolicy',
                                 verbose_name='phases of policy cycle',
                                 null=True,
                                 blank=True)

    additional_information_phase = TextField(
        ('additional information about the application'), blank=True)

    foresight_approaches = ManyToManyField(
        'ForesightApproaches', verbose_name='foresight approaches used')

    additional_information_foresight = TextField('additional information',
                                                 blank=True)

    stakeholder_participation = BooleanField('stakeholder participation',
                                             default=False)

    additional_information_stakeholder = TextField(
        'additional information about stakeholder involvement', blank=True)

    environmental_themes = ManyToManyField('common.EnvironmentalTheme',
                                           verbose_name='topics',
                                           blank=True)

    geographical_scope = ForeignKey('common.GeographicalScope',
                                    verbose_name='geographical scope',
                                    null=True,
                                    blank=True)

    countries = ManyToManyField('common.Country',
                                verbose_name='countries',
                                blank=True)

    def __unicode__(self):
        return self.title
Example #31
0
class Partie(AutoriteModel, UniqueSlugModel):
    """
    Partie de l’œuvre, c’est-à-dire typiquement un rôle ou un instrument pour
    une œuvre musicale.
    Pour plus de compréhensibilité, on affiche « rôle ou instrument » au lieu
    de « partie ».
    """
    nom = CharField(_('nom'),
                    max_length=200,
                    db_index=True,
                    help_text=_('Le nom d’une partie de la partition, '
                                'instrumentale ou vocale.'))
    nom_pluriel = CharField(_('nom (au pluriel)'),
                            max_length=230,
                            blank=True,
                            help_text=PLURAL_MSG)
    INSTRUMENT = 1
    ROLE = 2
    TYPES = (
        (INSTRUMENT, _('instrument')),
        (ROLE, _('rôle')),
    )
    type = PositiveSmallIntegerField(_('type'), choices=TYPES, db_index=True)
    # TODO: Ajouter automatiquement le rôle à l’effectif.
    oeuvre = ForeignKey('Oeuvre',
                        verbose_name=_('œuvre'),
                        blank=True,
                        null=True,
                        related_name='parties',
                        help_text=_('Ne remplir que pour les rôles.'),
                        on_delete=CASCADE)
    # TODO: Changer le verbose_name en un genre de "types de voix"
    # pour les rôles, mais en plus générique (ou un help_text).
    professions = ManyToManyField('Profession',
                                  related_name='parties',
                                  verbose_name=_('professions'),
                                  blank=True)
    parent = ForeignKey('self',
                        related_name='enfants',
                        blank=True,
                        null=True,
                        verbose_name=_('rôle ou instrument parent'),
                        on_delete=CASCADE)
    classement = SmallIntegerField(_('classement'), default=1, db_index=True)
    premier_interprete = ForeignKey(
        'Individu',
        related_name='parties_creees',
        on_delete=PROTECT,
        null=True,
        blank=True,
        verbose_name=_('premier(ère) interprète'),
    )

    class Meta(object):
        unique_together = ('nom', 'parent', 'oeuvre')
        verbose_name = _('rôle ou instrument')
        verbose_name_plural = _('rôles et instruments')
        ordering = (
            'type',
            'classement',
            'nom',
        )
        permissions = (('can_change_status', _('Peut changer l’état')), )

    def clean(self):
        if self.premier_interprete and (self.type == self.INSTRUMENT
                                        or self.type == self.ROLE
                                        and not self.oeuvre):
            raise ValidationError({
                'premier_interprete':
                _('Le premier interprète ne peut être rempli que pour '
                  'un rôle d’une œuvre donnée.'),
            })

    @staticmethod
    def invalidated_relations_when_saved(all_relations=False):
        return ('pupitres', )

    def interpretes(self):
        return self.elements_de_distribution.individus()

    def interpretes_html(self):
        return str_list([i.html() for i in self.interpretes()])

    interpretes_html.short_description = _('interprètes')

    def evenements(self):
        return self.elements_de_distribution.evenements()

    def repertoire(self):
        return self.pupitres.oeuvres()

    def get_children(self):
        return self.enfants.all()

    def is_leaf_node(self):
        return not self.enfants.exists()

    def pluriel(self):
        return calc_pluriel(self)

    @permalink
    def get_absolute_url(self):
        return 'partie_detail', (self.slug, )

    @permalink
    def permalien(self):
        return 'partie_permanent_detail', (self.pk, )

    def link(self):
        return self.html()

    def html(self, pluriel=False, oeuvre=True, tags=True):
        url = '' if not tags else self.get_absolute_url()
        if pluriel:
            out = self.pluriel()
        else:
            out = self.nom
        if oeuvre and self.oeuvre:
            out = f'{out} ({self.oeuvre})'
        return href(url, out, tags=tags)

    def __str__(self):
        return self.html(tags=False)

    def short_html(self, pluriel=False, tags=True):
        return self.html(pluriel=pluriel, oeuvre=False, tags=tags)

    @staticmethod
    def autocomplete_search_fields():
        return (
            'nom__unaccent__icontains',
            'nom_pluriel__unaccent__icontains',
            'professions__nom__unaccent__icontains',
            'professions__nom_pluriel__unaccent__icontains',
            'oeuvre__prefixe_titre__unaccent__icontains',
            'oeuvre__titre__unaccent__icontains',
            'oeuvre__coordination__unaccent__icontains',
            'oeuvre__prefixe_titre_secondaire__unaccent__icontains',
            'oeuvre__titre_secondaire__unaccent__icontains',
        )