Exemple #1
0
class XForm(BaseModel):
    CLONED_SUFFIX = '_cloned'
    MAX_ID_LENGTH = 100

    xls = models.FileField(upload_to=upload_to, null=True)
    json = models.TextField(default='')
    description = models.TextField(default='', null=True)
    xml = models.TextField()

    user = models.ForeignKey(User, related_name='xforms', null=True)
    require_auth = models.BooleanField(default=False)
    shared = models.BooleanField(default=False)
    shared_data = models.BooleanField(default=False)
    downloadable = models.BooleanField(default=True)
    allows_sms = models.BooleanField(default=False)
    encrypted = models.BooleanField(default=False)

    # the following fields are filled in automatically
    sms_id_string = models.SlugField(editable=False,
                                     verbose_name=ugettext_lazy("SMS ID"),
                                     max_length=MAX_ID_LENGTH,
                                     default='')
    id_string = models.SlugField(editable=False,
                                 verbose_name=ugettext_lazy("ID"),
                                 max_length=MAX_ID_LENGTH)
    title = models.CharField(editable=False, max_length=XFORM_TITLE_LENGTH)
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    last_submission_time = models.DateTimeField(blank=True, null=True)
    has_start_time = models.BooleanField(default=False)
    uuid = models.CharField(max_length=32, default='', db_index=True)

    uuid_regex = re.compile(r'(<instance>.*?id="[^"]+">)(.*</instance>)(.*)',
                            re.DOTALL)
    instance_id_regex = re.compile(r'<instance>.*?id="([^"]+)".*</instance>',
                                   re.DOTALL)
    uuid_node_location = 2
    uuid_bind_location = 4
    instances_with_geopoints = models.BooleanField(default=False)
    num_of_submissions = models.IntegerField(default=0)

    tags = TaggableManager()

    has_kpi_hooks = LazyDefaultBooleanField(default=False)
    kpi_asset_uid = models.CharField(max_length=32, null=True)

    class Meta:
        app_label = 'logger'
        unique_together = (("user", "id_string"), ("user", "sms_id_string"))
        verbose_name = ugettext_lazy("XForm")
        verbose_name_plural = ugettext_lazy("XForms")
        ordering = ("id_string", )
        permissions = (
            (CAN_VIEW_XFORM, _('Can view associated data')),
            (CAN_ADD_SUBMISSIONS, _('Can make submissions to the form')),
            (CAN_TRANSFER_OWNERSHIP, _('Can transfer form ownership.')),
            (CAN_VALIDATE_XFORM, _('Can validate submissions')),
            (CAN_DELETE_DATA_XFORM, _('Can delete submissions')),
        )

    def file_name(self):
        return self.id_string + ".xml"

    def url(self):
        return reverse("download_xform",
                       kwargs={
                           "username": self.user.username,
                           "id_string": self.id_string
                       })

    def data_dictionary(self):
        from onadata.apps.viewer.models.data_dictionary import\
            DataDictionary
        return DataDictionary.objects.get(pk=self.pk)

    @property
    def has_instances_with_geopoints(self):
        return self.instances_with_geopoints

    @property
    def kpi_hook_service(self):
        """
        Returns kpi hook service if it exists. XForm should have only one occurrence in any case.
        :return: RestService
        """
        return self.restservices.filter(name="kpi_hook").first()

    def _set_id_string(self):
        matches = self.instance_id_regex.findall(self.xml)
        if len(matches) != 1:
            raise XLSFormError(_("There should be a single id string."))
        self.id_string = matches[0]

    def _set_title(self):
        self.xml = smart_text(self.xml)
        text = re.sub(r'\s+', ' ', self.xml)
        matches = title_pattern.findall(text)
        title_xml = matches[0][:XFORM_TITLE_LENGTH]

        if len(matches) != 1:
            raise XLSFormError(_("There should be a single title."), matches)

        if self.title and title_xml != self.title:
            title_xml = self.title[:XFORM_TITLE_LENGTH]
            title_xml = saxutils.escape(title_xml)
            self.xml = title_pattern.sub("<h:title>%s</h:title>" % title_xml,
                                         self.xml)

        self.title = title_xml

    def _set_description(self):
        self.description = self.description \
            if self.description and self.description != '' else self.title

    def _set_encrypted_field(self):
        if self.json and self.json != '':
            json_dict = json.loads(self.json)
            if 'submission_url' in json_dict and 'public_key' in json_dict:
                self.encrypted = True
            else:
                self.encrypted = False

    def update(self, *args, **kwargs):
        super(XForm, self).save(*args, **kwargs)

    def save(self, *args, **kwargs):
        self._set_title()
        self._set_description()
        old_id_string = self.id_string
        self._set_id_string()
        self._set_encrypted_field()
        # check if we have an existing id_string,
        # if so, the one must match but only if xform is NOT new
        if self.pk and old_id_string and old_id_string != self.id_string:
            raise XLSFormError(
                _("Your updated form's id_string '%(new_id)s' must match "
                  "the existing forms' id_string '%(old_id)s'." % {
                      'new_id': self.id_string,
                      'old_id': old_id_string
                  }))

        if getattr(settings, 'STRICT', True) and \
                not re.search(r"^[\w-]+$", self.id_string):
            raise XLSFormError(
                _('In strict mode, the XForm ID must be a '
                  'valid slug and contain no spaces.'))

        if not self.sms_id_string:
            try:
                # try to guess the form's wanted sms_id_string
                # from it's json rep (from XLSForm)
                # otherwise, use id_string to ensure uniqueness
                self.sms_id_string = json.loads(self.json).get(
                    'sms_keyword', self.id_string)
            except:
                self.sms_id_string = self.id_string

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

    def __unicode__(self):
        return getattr(self, "id_string", "")

    def submission_count(self, force_update=False):
        if self.num_of_submissions == 0 or force_update:
            count = self.instances.filter(deleted_at__isnull=True).count()
            self.num_of_submissions = count
            self.save(update_fields=['num_of_submissions'])
        return self.num_of_submissions

    submission_count.short_description = ugettext_lazy("Submission Count")

    def geocoded_submission_count(self):
        """Number of geocoded submissions."""
        return self.instances.filter(deleted_at__isnull=True,
                                     geom__isnull=False).count()

    def time_of_last_submission(self):
        if self.last_submission_time is None and self.num_of_submissions > 0:
            try:
                last_submission = self.instances.\
                    filter(deleted_at__isnull=True).latest("date_created")
            except ObjectDoesNotExist:
                pass
            else:
                self.last_submission_time = last_submission.date_created
                self.save()
        return self.last_submission_time

    def time_of_last_submission_update(self):
        try:
            # we also consider deleted instances in this case
            return self.instances.latest("date_modified").date_modified
        except ObjectDoesNotExist:
            pass

    @property
    def hash(self):
        return '%s' % md5(self.xml.encode('utf8')).hexdigest()

    @property
    def can_be_replaced(self):
        if hasattr(self.submission_count, '__call__'):
            num_submissions = self.submission_count()
        else:
            num_submissions = self.submission_count
        return num_submissions == 0

    @classmethod
    def public_forms(cls):
        return cls.objects.filter(shared=True)

    def _xls_file_io(self):
        """
        pulls the xls file from remote storage

        this should be used sparingly
        """
        file_path = self.xls.name
        default_storage = get_storage_class()()

        if file_path != '' and default_storage.exists(file_path):
            with default_storage.open(file_path) as ff:
                if file_path.endswith('.csv'):
                    return convert_csv_to_xls(ff.read())
                else:
                    return StringIO(ff.read())

    def to_kpi_content_schema(self):
        """
        Parses xlsform structure into json representation
        of spreadsheet structure.
        """
        if not xls_to_dicts:
            raise ImportError('formpack module needed')
        content = xls_to_dicts(self._xls_file_io())
        # a temporary fix to the problem of list_name alias
        return json.loads(
            re.sub('list name', 'list_name', json.dumps(content, indent=4)))

    def to_xlsform(self):
        """
        Generate an XLS format XLSForm copy of this form.
        """
        file_path = self.xls.name
        default_storage = get_storage_class()()

        if file_path != '' and default_storage.exists(file_path):
            with default_storage.open(file_path) as xlsform_file:
                if file_path.endswith('.csv'):
                    xlsform_io = convert_csv_to_xls(xlsform_file.read())
                else:
                    xlsform_io = io.BytesIO(xlsform_file.read())
            return xlsform_io
        else:
            return None

    @property
    def settings(self):
        """
        Mimic Asset settings.
        :return: Object
        """
        # As soon as we need to add custom validation statuses in Asset settings,
        # validation in add_validation_status_to_instance
        # (kobocat/onadata/apps/api/tools.py) should still work
        default_validation_statuses = getattr(settings,
                                              "DEFAULT_VALIDATION_STATUSES",
                                              [])

        # Later purpose, default_validation_statuses could be merged with a custom validation statuses dict
        # for example:
        #   self._validation_statuses.update(default_validation_statuses)

        return {"validation_statuses": default_validation_statuses}
Exemple #2
0
class Cable(ChangeLoggedModel, CustomFieldModel):
    """
    A physical connection between two endpoints.
    """
    termination_a_type = models.ForeignKey(
        to=ContentType,
        limit_choices_to=CABLE_TERMINATION_MODELS,
        on_delete=models.PROTECT,
        related_name='+')
    termination_a_id = models.PositiveIntegerField()
    termination_a = GenericForeignKey(ct_field='termination_a_type',
                                      fk_field='termination_a_id')
    termination_b_type = models.ForeignKey(
        to=ContentType,
        limit_choices_to=CABLE_TERMINATION_MODELS,
        on_delete=models.PROTECT,
        related_name='+')
    termination_b_id = models.PositiveIntegerField()
    termination_b = GenericForeignKey(ct_field='termination_b_type',
                                      fk_field='termination_b_id')
    type = models.CharField(max_length=50,
                            choices=CableTypeChoices,
                            blank=True)
    status = models.CharField(max_length=50,
                              choices=CableStatusChoices,
                              default=CableStatusChoices.STATUS_CONNECTED)
    label = models.CharField(max_length=100, blank=True)
    color = ColorField(blank=True)
    length = models.PositiveSmallIntegerField(blank=True, null=True)
    length_unit = models.CharField(
        max_length=50,
        choices=CableLengthUnitChoices,
        blank=True,
    )
    # Stores the normalized length (in meters) for database ordering
    _abs_length = models.DecimalField(max_digits=10,
                                      decimal_places=4,
                                      blank=True,
                                      null=True)
    # Cache the associated device (where applicable) for the A and B terminations. This enables filtering of Cables by
    # their associated Devices.
    _termination_a_device = models.ForeignKey(to=Device,
                                              on_delete=models.CASCADE,
                                              related_name='+',
                                              blank=True,
                                              null=True)
    _termination_b_device = models.ForeignKey(to=Device,
                                              on_delete=models.CASCADE,
                                              related_name='+',
                                              blank=True,
                                              null=True)
    tags = TaggableManager(through=TaggedItem)

    objects = RestrictedQuerySet.as_manager()

    csv_headers = [
        'termination_a_type',
        'termination_a_id',
        'termination_b_type',
        'termination_b_id',
        'type',
        'status',
        'label',
        'color',
        'length',
        'length_unit',
    ]

    class Meta:
        ordering = ['pk']
        unique_together = (
            ('termination_a_type', 'termination_a_id'),
            ('termination_b_type', 'termination_b_id'),
        )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # A copy of the PK to be used by __str__ in case the object is deleted
        self._pk = self.pk

        # Cache the original status so we can check later if it's been changed
        self._orig_status = self.status

    @classmethod
    def from_db(cls, db, field_names, values):
        """
        Cache the original A and B terminations of existing Cable instances for later reference inside clean().
        """
        instance = super().from_db(db, field_names, values)

        instance._orig_termination_a_type_id = instance.termination_a_type_id
        instance._orig_termination_a_id = instance.termination_a_id
        instance._orig_termination_b_type_id = instance.termination_b_type_id
        instance._orig_termination_b_id = instance.termination_b_id

        return instance

    def __str__(self):
        pk = self.pk or self._pk
        return self.label or f'#{pk}'

    def get_absolute_url(self):
        return reverse('dcim:cable', args=[self.pk])

    def clean(self):
        from circuits.models import CircuitTermination

        super().clean()

        # Validate that termination A exists
        if not hasattr(self, 'termination_a_type'):
            raise ValidationError('Termination A type has not been specified')
        try:
            self.termination_a_type.model_class().objects.get(
                pk=self.termination_a_id)
        except ObjectDoesNotExist:
            raise ValidationError({
                'termination_a':
                'Invalid ID for type {}'.format(self.termination_a_type)
            })

        # Validate that termination B exists
        if not hasattr(self, 'termination_b_type'):
            raise ValidationError('Termination B type has not been specified')
        try:
            self.termination_b_type.model_class().objects.get(
                pk=self.termination_b_id)
        except ObjectDoesNotExist:
            raise ValidationError({
                'termination_b':
                'Invalid ID for type {}'.format(self.termination_b_type)
            })

        # If editing an existing Cable instance, check that neither termination has been modified.
        if self.pk:
            err_msg = 'Cable termination points may not be modified. Delete and recreate the cable instead.'
            if (self.termination_a_type_id != self._orig_termination_a_type_id
                    or self.termination_a_id != self._orig_termination_a_id):
                raise ValidationError({'termination_a': err_msg})
            if (self.termination_b_type_id != self._orig_termination_b_type_id
                    or self.termination_b_id != self._orig_termination_b_id):
                raise ValidationError({'termination_b': err_msg})

        type_a = self.termination_a_type.model
        type_b = self.termination_b_type.model

        # Validate interface types
        if type_a == 'interface' and self.termination_a.type in NONCONNECTABLE_IFACE_TYPES:
            raise ValidationError({
                'termination_a_id':
                'Cables cannot be terminated to {} interfaces'.format(
                    self.termination_a.get_type_display())
            })
        if type_b == 'interface' and self.termination_b.type in NONCONNECTABLE_IFACE_TYPES:
            raise ValidationError({
                'termination_b_id':
                'Cables cannot be terminated to {} interfaces'.format(
                    self.termination_b.get_type_display())
            })

        # Check that termination types are compatible
        if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
            raise ValidationError(
                f"Incompatible termination types: {self.termination_a_type} and {self.termination_b_type}"
            )

        # Check that two connected RearPorts have the same number of positions (if both are >1)
        if isinstance(self.termination_a, RearPort) and isinstance(
                self.termination_b, RearPort):
            if self.termination_a.positions > 1 and self.termination_b.positions > 1:
                if self.termination_a.positions != self.termination_b.positions:
                    raise ValidationError(
                        f"{self.termination_a} has {self.termination_a.positions} position(s) but "
                        f"{self.termination_b} has {self.termination_b.positions}. "
                        f"Both terminations must have the same number of positions (if greater than one)."
                    )

        # A termination point cannot be connected to itself
        if self.termination_a == self.termination_b:
            raise ValidationError(
                f"Cannot connect {self.termination_a_type} to itself")

        # A front port cannot be connected to its corresponding rear port
        if (type_a in ['frontport', 'rearport']
                and type_b in ['frontport', 'rearport'] and
            (getattr(self.termination_a, 'rear_port', None)
             == self.termination_b or getattr(self.termination_b, 'rear_port',
                                              None) == self.termination_a)):
            raise ValidationError(
                "A front port cannot be connected to it corresponding rear port"
            )

        # Check for an existing Cable connected to either termination object
        if self.termination_a.cable not in (None, self):
            raise ValidationError(
                "{} already has a cable attached (#{})".format(
                    self.termination_a, self.termination_a.cable_id))
        if self.termination_b.cable not in (None, self):
            raise ValidationError(
                "{} already has a cable attached (#{})".format(
                    self.termination_b, self.termination_b.cable_id))

        # Validate length and length_unit
        if self.length is not None and not self.length_unit:
            raise ValidationError(
                "Must specify a unit when setting a cable length")
        elif self.length is None:
            self.length_unit = ''

    def save(self, *args, **kwargs):

        # Store the given length (if any) in meters for use in database ordering
        if self.length and self.length_unit:
            self._abs_length = to_meters(self.length, self.length_unit)
        else:
            self._abs_length = None

        # Store the parent Device for the A and B terminations (if applicable) to enable filtering
        if hasattr(self.termination_a, 'device'):
            self._termination_a_device = self.termination_a.device
        if hasattr(self.termination_b, 'device'):
            self._termination_b_device = self.termination_b.device

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

        # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk)
        self._pk = self.pk

    def to_csv(self):
        return (
            '{}.{}'.format(self.termination_a_type.app_label,
                           self.termination_a_type.model),
            self.termination_a_id,
            '{}.{}'.format(self.termination_b_type.app_label,
                           self.termination_b_type.model),
            self.termination_b_id,
            self.get_type_display(),
            self.get_status_display(),
            self.label,
            self.color,
            self.length,
            self.length_unit,
        )

    def get_status_class(self):
        return CableStatusChoices.CSS_CLASSES.get(self.status)

    def get_compatible_types(self):
        """
        Return all termination types compatible with termination A.
        """
        if self.termination_a is None:
            return
        return COMPATIBLE_TERMINATION_TYPES[
            self.termination_a._meta.model_name]
class Page(MPTTModel):
    """
    This model contain the status, dates, author, template.
    The real content of the page can be found in the
    :class:`Content <pages.models.Content>` model.

    .. attribute:: creation_date
       When the page has been created.

    .. attribute:: publication_date
       When the page should be visible.

    .. attribute:: publication_end_date
       When the publication of this page end.

    .. attribute:: last_modification_date
       Last time this page has been modified.

    .. attribute:: status
       The current status of the page. Could be DRAFT, PUBLISHED,
       EXPIRED or HIDDEN. You should the property :attr:`calculated_status` if
       you want that the dates are taken in account.

    .. attribute:: template
       A string containing the name of the template file for this page.
    """

    # some class constants to refer to, e.g. Page.DRAFT
    DRAFT = 0
    PUBLISHED = 1
    EXPIRED = 2
    HIDDEN = 3
    STATUSES = (
        (PUBLISHED, _('Published')),
        (HIDDEN, _('Hidden')),
        (DRAFT, _('Draft')),
    )

    PAGE_LANGUAGES_KEY = "page_%d_languages"
    PAGE_URL_KEY = "page_%d_url"
    PAGE_BROKEN_LINK_KEY = "page_broken_link_%s"

    author = models.ForeignKey(User, verbose_name=_('author'))

    parent = models.ForeignKey('self',
                               null=True,
                               blank=True,
                               related_name='children',
                               verbose_name=_('parent'))
    creation_date = models.DateTimeField(_('creation date'),
                                         editable=False,
                                         default=datetime.now)
    publication_date = models.DateTimeField(
        _('publication date'),
        null=True,
        blank=True,
        help_text=_('''When the page should go
            live. Status must be "Published" for page to go live.'''))
    publication_end_date = models.DateTimeField(
        _('publication end date'),
        null=True,
        blank=True,
        help_text=_('''When to expire the page.
            Leave empty to never expire.'''))

    last_modification_date = models.DateTimeField(_('last modification date'))

    status = models.IntegerField(_('status'), choices=STATUSES, default=DRAFT)
    template = models.CharField(_('template'),
                                max_length=100,
                                null=True,
                                blank=True)

    delegate_to = models.CharField(_('delegate to'),
                                   max_length=100,
                                   null=True,
                                   blank=True)

    freeze_date = models.DateTimeField(_('freeze date'),
                                       null=True,
                                       blank=True,
                                       help_text=_('''Don't publish any content
            after this date.'''))

    if settings.PAGE_USE_SITE_ID:
        sites = models.ManyToManyField(
            Site,
            default=[settings.SITE_ID],
            help_text=_('The site(s) the page is accessible at.'),
            verbose_name=_('sites'))

    redirect_to_url = models.CharField(max_length=200, null=True, blank=True)

    redirect_to = models.ForeignKey('self',
                                    null=True,
                                    blank=True,
                                    related_name='redirected_pages')

    only_authenticated_users = models.BooleanField(default=False,
                                                   help_text='''If
        True only authenticated users can access this page''')

    permission = models.ForeignKey(
        Permission,
        related_name='accessible_pages',
        null=True,
        blank=True,
        help_text=_('''Permission needed to access the
            page'''))

    # Managers
    objects = PageManager()

    if settings.PAGE_TAGGING:
        tags = TaggableManager(blank=True)

    class Meta:
        """Make sure the default page ordering is correct."""
        ordering = ['tree_id', 'lft']
        get_latest_by = "publication_date"
        verbose_name = _('page')
        verbose_name_plural = _('pages')
        permissions = settings.PAGE_EXTRA_PERMISSIONS

    def __init__(self, *args, **kwargs):
        """Instanciate the page object."""
        # per instance cache
        self._languages = None
        self._complete_slug = None
        self._content_dict = None
        self._is_first_root = None
        super(Page, self).__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        """Override the default ``save`` method."""
        if not self.status:
            self.status = self.DRAFT
        # Published pages should always have a publication date
        if self.publication_date is None and self.status == self.PUBLISHED:
            self.publication_date = datetime.now()
        # Drafts should not, unless they have been set to the future
        if self.status == self.DRAFT:
            if settings.PAGE_SHOW_START_DATE:
                if (self.publication_date
                        and self.publication_date <= datetime.now()):
                    self.publication_date = None
            else:
                self.publication_date = None
        self.last_modification_date = datetime.now()
        # let's assume there is no more broken links after a save
        cache.delete(self.PAGE_BROKEN_LINK_KEY % self.id)
        super(Page, self).save(*args, **kwargs)
        # fix sites many-to-many link when the're hidden from the form
        if settings.PAGE_HIDE_SITES and self.sites.count() == 0:
            self.sites.add(Site.objects.get(pk=settings.SITE_ID))

    def _get_calculated_status(self):
        """Get the calculated status of the page based on
        :attr:`Page.publication_date`,
        :attr:`Page.publication_end_date`,
        and :attr:`Page.status`."""
        if settings.PAGE_SHOW_START_DATE and self.publication_date:
            if self.publication_date > datetime.now():
                return self.DRAFT

        if settings.PAGE_SHOW_END_DATE and self.publication_end_date:
            if self.publication_end_date < datetime.now():
                return self.EXPIRED

        return self.status

    calculated_status = property(_get_calculated_status)

    def _visible(self):
        """Return True if the page is visible on the frontend."""
        return self.calculated_status in (self.PUBLISHED, self.HIDDEN)

    visible = property(_visible)

    def get_children_for_frontend(self):
        """Return a :class:`QuerySet` of published children page"""
        return Page.objects.filter_published(self.get_children())

    def get_date_ordered_children_for_frontend(self):
        """Return a :class:`QuerySet` of published children page ordered
        by publication date."""
        return self.get_children_for_frontend().order_by('-publication_date')

    def invalidate(self):
        """Invalidate cached data for this page."""

        cache.delete(self.PAGE_LANGUAGES_KEY % (self.id))
        cache.delete('PAGE_FIRST_ROOT_ID')
        self._languages = None
        self._complete_slug = None
        self._content_dict = dict()

        p_names = [p.name for p in get_placeholders(self.get_template())]
        if 'slug' not in p_names:
            p_names.append('slug')
        if 'title' not in p_names:
            p_names.append('title')
        # delete content cache, frozen or not
        for name in p_names:
            # frozen
            cache.delete(PAGE_CONTENT_DICT_KEY % (self.id, name, 1))
            # not frozen
            cache.delete(PAGE_CONTENT_DICT_KEY % (self.id, name, 0))

        cache.delete(self.PAGE_URL_KEY % (self.id))

    def get_languages(self):
        """
        Return a list of all used languages for this page.
        """
        if self._languages:
            return self._languages
        self._languages = cache.get(self.PAGE_LANGUAGES_KEY % (self.id))
        if self._languages is not None:
            return self._languages

        languages = [
            c['language'] for c in Content.objects.filter(
                page=self, type="slug").values('language')
        ]
        # remove duplicates
        languages = list(set(languages))
        languages.sort()
        cache.set(self.PAGE_LANGUAGES_KEY % (self.id), languages)
        self._languages = languages
        return languages

    def is_first_root(self):
        """Return ``True`` if this page is the first root pages."""
        if self.parent:
            return False
        if self._is_first_root is not None:
            return self._is_first_root
        first_root_id = cache.get('PAGE_FIRST_ROOT_ID')
        if first_root_id is not None:
            self._is_first_root = first_root_id == self.id
            return self._is_first_root
        try:
            first_root_id = Page.objects.root().values('id')[0]['id']
        except IndexError:
            first_root_id = None
        if first_root_id is not None:
            cache.set('PAGE_FIRST_ROOT_ID', first_root_id)
        self._is_first_root = self.id == first_root_id
        return self._is_first_root

    def get_url_path(self, language=None):
        """Return the URL's path component. Add the language prefix if
        ``PAGE_USE_LANGUAGE_PREFIX`` setting is set to ``True``.

        :param language: the wanted url language.
        """
        if self.is_first_root():
            # this is used to allow users to change URL of the root
            # page. The language prefix is not usable here.
            try:
                return reverse('pages-root')
            except Exception:
                pass
        url = self.get_complete_slug(language)
        if not language:
            language = settings.PAGE_DEFAULT_LANGUAGE
        if settings.PAGE_USE_LANGUAGE_PREFIX:
            return reverse('pages-details-by-path', args=[language, url])
        else:
            return reverse('pages-details-by-path', args=[url])

    def get_absolute_url(self, language=None):
        """Alias for `get_url_path`.

        This method is only there for backward compatibility and will be
        removed in a near futur.

        :param language: the wanted url language.
        """
        return self.get_url_path(language=language)

    def get_complete_slug(self, language=None, hideroot=True):
        """Return the complete slug of this page by concatenating
        all parent's slugs.

        :param language: the wanted slug language."""
        if not language:
            language = settings.PAGE_DEFAULT_LANGUAGE

        if self._complete_slug and language in self._complete_slug:
            return self._complete_slug[language]

        self._complete_slug = cache.get(self.PAGE_URL_KEY % (self.id))
        if self._complete_slug is None:
            self._complete_slug = {}
        elif language in self._complete_slug:
            return self._complete_slug[language]

        if hideroot and settings.PAGE_HIDE_ROOT_SLUG and self.is_first_root():
            url = u''
        else:
            url = u'%s' % self.slug(language)
        for ancestor in self.get_ancestors(ascending=True):
            url = ancestor.slug(language) + u'/' + url

        self._complete_slug[language] = url
        cache.set(self.PAGE_URL_KEY % (self.id), self._complete_slug)
        return url

    def get_url(self, language=None):
        """Alias for `get_complete_slug`.

        This method is only there for backward compatibility and will be
        removed in a near futur.

        :param language: the wanted url language.
        """
        return self.get_complete_slug(language=language)

    def slug(self, language=None, fallback=True):
        """
        Return the slug of the page depending on the given language.

        :param language: wanted language, if not defined default is used.
        :param fallback: if ``True``, the slug will also be searched in other \
        languages.
        """

        slug = self.get_content(language, 'slug', language_fallback=fallback)

        return slug

    def title(self, language=None, fallback=True):
        """
        Return the title of the page depending on the given language.

        :param language: wanted language, if not defined default is used.
        :param fallback: if ``True``, the slug will also be searched in \
        other languages.
        """
        if not language:
            language = settings.PAGE_DEFAULT_LANGUAGE

        return self.get_content(language, 'title', language_fallback=fallback)

    def get_content(self, language, ctype, language_fallback=False):
        """Shortcut method for retrieving a piece of page content

        :param language: wanted language, if not defined default is used.
        :param ctype: the type of content.
        :param fallback: if ``True``, the content will also be searched in \
        other languages.
        """
        return Content.objects.get_content(self, language, ctype,
                                           language_fallback)

    def expose_content(self):
        """Return all the current content of this page into a `string`.

        This is used by the haystack framework to build the search index."""
        placeholders = get_placeholders(self.get_template())
        exposed_content = []
        for lang in self.get_languages():
            for ctype in [p.name for p in placeholders]:
                content = self.get_content(lang, ctype, False)
                if content:
                    exposed_content.append(content)
        return u"\r\n".join(exposed_content)

    def content_by_language(self, language):
        """
        Return a list of latest published
        :class:`Content <pages.models.Content>`
        for a particluar language.

        :param language: wanted language,
        """
        placeholders = get_placeholders(self.get_template())
        content_list = []
        for ctype in [p.name for p in placeholders]:
            try:
                content = Content.objects.get_content_object(
                    self, language, ctype)
                content_list.append(content)
            except Content.DoesNotExist:
                pass
        return content_list

    def get_template(self):
        """
        Get the :attr:`template <Page.template>` of this page if
        defined or the closer parent's one if defined
        or :attr:`pages.settings.PAGE_DEFAULT_TEMPLATE` otherwise.
        """
        if self.template:
            return self.template

        template = None
        for p in self.get_ancestors(ascending=True):
            if p.template:
                template = p.template
                break

        if not template:
            template = settings.PAGE_DEFAULT_TEMPLATE

        return template

    def get_template_name(self):
        """
        Get the template name of this page if defined or if a closer
        parent has a defined template or
        :data:`pages.settings.PAGE_DEFAULT_TEMPLATE` otherwise.
        """
        template = self.get_template()
        page_templates = settings.get_page_templates()
        for t in page_templates:
            if t[0] == template:
                return t[1]
        return template

    def has_broken_link(self):
        """
        Return ``True`` if the page have broken links to other pages
        into the content.
        """
        return cache.get(self.PAGE_BROKEN_LINK_KEY % self.id)

    def valid_targets(self):
        """Return a :class:`QuerySet` of valid targets for moving a page
        into the tree.

        :param perms: the level of permission of the concerned user.
        """
        exclude_list = [self.id]
        for p in self.get_descendants():
            exclude_list.append(p.id)
        return Page.objects.exclude(id__in=exclude_list)

    def slug_with_level(self, language=None):
        """Display the slug of the page prepended with insecable
        spaces equal to simluate the level of page in the hierarchy."""
        level = ''
        if self.level:
            for n in range(0, self.level):
                level += '&nbsp;&nbsp;&nbsp;'
        return mark_safe(level + self.slug(language))

    def margin_level(self):
        """Used in the admin menu to create the left margin."""
        return self.level * 2

    def __unicode__(self):
        """Representation of the page, saved or not."""
        if self.id:
            # without ID a slug cannot be retrieved
            slug = self.slug()
            if slug:
                return slug
            return u"Page %d" % self.id
        return u"Page without id"
Exemple #4
0
class Job(models.Model):
    """
    Model to create a Job.

    Attributes:
        date_created (datetime): Datetime of job creation
        date_modified (datetime): Datetime of job modified
        document (file): Job description file
        freelancer (user):  Freelancer whom the job assigned.
        job_description (str): Job description
        job_title (str): Job title
        owner (user): User who owns the job
        price (decimal): Job price
        status (str): Job current status
        tags (str): Tags representing job

    """
    owner = models.ForeignKey(
        'users.User',
        on_delete=models.CASCADE,
        related_name='job_owner'
    )
    freelancer = models.ForeignKey(
        'users.User',
        null=True, blank=True,
        on_delete=models.CASCADE,
        related_name="job_freelancer"
    )

    job_title = models.CharField(max_length=300)
    job_description = models.TextField()
    price = models.DecimalField(max_digits=8, decimal_places=2)
    tags = TaggableManager()

    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

    document = models.FileField(upload_to='attachments', blank=True, null=True)

    ACTIVE = 'active'
    WORKING = 'working'
    ENDED = 'ended'
    CHOICES = ((ACTIVE, 'active'), (WORKING, 'working'), (ENDED, 'ended'))

    status = models.CharField(max_length=9, choices=CHOICES, default=ACTIVE)

    class Meta:
        verbose_name = 'job'
        verbose_name_plural = 'jobs'
        unique_together = ('owner', 'date_created')

    def __str__(self):
        return "%s - %s - %s" % (
            self.owner.get_full_name(),
            self.freelancer.get_full_name() if self.freelancer else '',
            self.status
        )

    @property
    def freelancers(self):
        """
        It prepares all the freelancers of the current job.
        """
        proposals = self.job_proposal.all()
        return  [proposal.freelancer for proposal in proposals]
Exemple #5
0
class Post(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
    title = models.CharField(max_length=120)
    slug = models.SlugField(unique=True)
    image = models.ImageField(upload_to=upload_location,
                              null=True,
                              blank=True,
                              width_field="width_field",
                              height_field="height_field")
    likes = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   blank=True,
                                   related_name='post_likes')
    height_field = models.IntegerField(default=0)
    width_field = models.IntegerField(default=0)
    content = models.TextField()
    draft = models.BooleanField(default=False)
    publish = models.DateField(auto_now=False, auto_now_add=False)
    updated = models.DateTimeField(auto_now=True, auto_now_add=False)
    timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
    community = models.ForeignKey(Communities, on_delete=models.CASCADE)
    language = models.CharField(max_length=120, blank=True)
    source = models.CharField(max_length=120, blank=True)
    summary = models.CharField(max_length=120, blank=True)
    tags = TaggableManager(blank=True)

    def __unicode__(self):
        return self.name

    objects = PostManager()

    def __unicode__(self):
        return self.title

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("posts:detail", kwargs={"slug": self.slug})

    # def get_api_url(self):
    #     return reverse("posts-api:detail", kwargs={"slug": self.slug})

    def get_like_url(self):
        return reverse("posts:like-toggle", kwargs={"slug": self.slug})

    def get_api_like_url(self):
        return reverse("posts:like-api-toggle", kwargs={"slug": self.slug})

    class Meta:
        ordering = ["-timestamp", "-updated"]

    @property
    def comments(self):
        instance = self
        qs = Comment.objects.filter_by_instance(instance)
        return qs

    @property
    def communities(self):
        instance = self
        qs = Communities.objects.filter_by_instance(instance)
        return qs

    @property
    def get_content_type(self):
        instance = self
        content_type = ContentType.objects.get_for_model(instance.__class__)
        return content_type
Exemple #6
0
class Issue(GcdData):
    class Meta:
        app_label = 'gcd'
        ordering = ['series', 'sort_code']
        unique_together = ('series', 'sort_code')

    # Issue identification
    number = models.CharField(max_length=50, db_index=True)
    title = models.CharField(max_length=255, db_index=True)
    no_title = models.BooleanField(default=False, db_index=True)
    volume = models.CharField(max_length=50, db_index=True)
    no_volume = models.BooleanField(default=False, db_index=True)
    volume_not_printed = models.BooleanField(default=False)
    display_volume_with_number = models.BooleanField(default=False,
                                                     db_index=True)
    isbn = models.CharField(max_length=32, db_index=True)
    no_isbn = models.BooleanField(default=False, db_index=True)
    valid_isbn = models.CharField(max_length=13, db_index=True)
    variant_of = models.ForeignKey('self',
                                   null=True,
                                   related_name='variant_set')
    variant_name = models.CharField(max_length=255)
    barcode = models.CharField(max_length=38, db_index=True)
    no_barcode = models.BooleanField(default=False)
    rating = models.CharField(max_length=255, default='', db_index=True)
    no_rating = models.BooleanField(default=False, db_index=True)

    # Dates and sorting
    publication_date = models.CharField(max_length=255)
    key_date = models.CharField(max_length=10, db_index=True)
    on_sale_date = models.CharField(max_length=10, db_index=True)
    on_sale_date_uncertain = models.BooleanField(default=False)
    sort_code = models.IntegerField(db_index=True)
    indicia_frequency = models.CharField(max_length=255)
    no_indicia_frequency = models.BooleanField(default=False, db_index=True)

    # Price, page count and format fields
    price = models.CharField(max_length=255)
    page_count = models.DecimalField(max_digits=10,
                                     decimal_places=3,
                                     null=True)
    page_count_uncertain = models.BooleanField(default=False)

    editing = models.TextField()
    no_editing = models.BooleanField(default=False, db_index=True)
    notes = models.TextField()

    keywords = TaggableManager()

    # Series and publisher links
    series = models.ForeignKey('Series')
    indicia_publisher = models.ForeignKey(IndiciaPublisher, null=True)
    indicia_pub_not_printed = models.BooleanField(default=False)
    brand = models.ForeignKey(Brand, null=True)
    no_brand = models.BooleanField(default=False, db_index=True)
    image_resources = GenericRelation(Image)

    # In production, this is a tinyint(1) because the set of numbers
    # is very small.  But syncdb produces an int(11).
    is_indexed = models.IntegerField(default=0, db_index=True)

    @property
    def indicia_image(self):
        img = Image.objects.filter(
            object_id=self.id,
            deleted=False,
            content_type=ContentType.objects.get_for_model(self),
            type__id=1)
        if img:
            return img.get()
        else:
            return None

    @property
    def soo_image(self):
        img = Image.objects.filter(
            object_id=self.id,
            deleted=False,
            content_type=ContentType.objects.get_for_model(self),
            type__id=2)
        if img:
            return img.get()
        else:
            return None

    def active_stories(self):
        return self.story_set.exclude(deleted=True)

    def _active_variants(self):
        return self.variant_set.exclude(deleted=True)

    def active_variants(self):
        return self._active_variants()

    def shown_stories(self):
        """ returns cover sequence and story sequences """
        if self.variant_of:
            stories_from = self.variant_of
        else:
            stories_from = self
        stories = list(stories_from.active_stories().order_by(
            'sequence_number').select_related('type', 'migration_status'))
        if self.series.is_comics_publication:
            if (len(stories) > 0):
                cover_story = stories.pop(0)
                if self.variant_of:
                    # can have only one sequence, the variant cover
                    if self.active_stories().count():
                        cover_story = self.active_stories()[0]
            elif self.variant_of and len(list(self.active_stories())):
                cover_story = self.active_stories()[0]
            else:
                cover_story = None
        else:
            cover_story = None
        return cover_story, stories

    def _active_covers(self):
        if self.can_have_cover():
            return self.cover_set.exclude(deleted=True)
        else:
            return self.cover_set.none()

    def active_covers(self):
        return self._active_covers()

    def variant_covers(self):
        """ returns the images from the variant issues """
        from .cover import Cover
        if self.variant_of:
            variant_issues = list(self.variant_of.active_variants().exclude(
                id=self.id).values_list('id', flat=True))
        else:
            variant_issues = list(self.active_variants().values_list(
                'id', flat=True))
        variant_covers = Cover.objects.filter(issue__id__in=variant_issues)\
                                      .exclude(deleted=True)
        if self.variant_of:
            variant_covers |= self.variant_of.active_covers()
        return variant_covers

    def shown_covers(self):
        return self.active_covers(), self.variant_covers()

    def has_covers(self):
        return self.can_have_cover() and self.active_covers().exists()

    def can_have_cover(self):
        if self.series.is_comics_publication:
            return True
        if self.is_indexed in [INDEXED['full'], INDEXED['ten_percent']]:
            return True
        else:
            return False

    def other_variants(self):
        if self.variant_of:
            variants = self.variant_of.active_variants().exclude(id=self.id)
        else:
            variants = self.active_variants()
        return list(variants)

    def _get_prev_next_issue(self):
        """
        Find the issues immediately before and after the given issue.
        """

        prev_issue = None
        next_issue = None

        earlier_issues = self.series.active_base_issues()\
                             .filter(sort_code__lt=self.sort_code)
        earlier_issues = earlier_issues.order_by('-sort_code')
        if earlier_issues:
            prev_issue = earlier_issues[0]

        later_issues = self.series.active_base_issues()\
                           .filter(sort_code__gt=self.sort_code)
        later_issues = later_issues.order_by('sort_code')
        if later_issues:
            next_issue = later_issues[0]

        return [prev_issue, next_issue]

    def get_prev_next_issue(self):
        return self._get_prev_next_issue()

    def has_reprints(self, ignore=STORY_TYPES['preview']):
        """Simplifies UI checks for conditionals, notes and reprint fields"""
        return self.from_reprints.count() or \
            self.to_reprints.exclude(target__type__id=ignore).count() or \
            self.from_issue_reprints.count() or \
            self.to_issue_reprints.count()

    def has_variants(self):
        return self.active_variants().exists()

    def has_dependents(self):
        has_non_story_deps = (
            self.has_variants() or self.has_reprints(ignore=None)
            or self.cover_revisions.active_set().exists()
            or self.variant_revisions.active_set().exists()
            or self.origin_reprint_revisions.active_set().exists()
            or self.target_reprint_revisions.active_set().exists())
        if has_non_story_deps:
            return True

        for story in self.active_stories():
            has_story_deps = (
                story.has_reprints(notes=False)
                or story.origin_reprint_revisions.active_set().exists()
                or story.target_reprint_revisions.active_set().exists())
            if has_story_deps:
                return True

        return False

    def can_upload_variants(self):
        if self.has_covers():
            currently_deleting = self.revisions.active_set() \
                                               .filter(deleted=True).exists()
            return not currently_deleting
        else:
            return False

    def set_indexed_status(self):
        """
        Sets the index status and returns the resulting stat change value.

        The return value of this method is intended for use in adjusting
        the "issue indexes" stat count.  GCD model modules cannot import
        CountStats and set them directly due to circular dependencies.
        """
        was_indexed = self.is_indexed

        if not self.variant_of:
            is_indexed = INDEXED['skeleton']
            if self.page_count > 0:
                total_count = self.active_stories()\
                              .aggregate(Sum('page_count'))['page_count__sum']
                if (total_count > 0
                        and total_count >= Decimal('0.4') * self.page_count):
                    is_indexed = INDEXED['full']
                elif (total_count > 0
                      and total_count >= Decimal('0.1') * self.page_count):
                    is_indexed = INDEXED['ten_percent']

            if (is_indexed not in [INDEXED['full'], INDEXED['ten_percent']] and
                    self.active_stories().filter(type=StoryType.objects.get(
                        name='comic story')).exists()):
                is_indexed = INDEXED['partial']

            if is_indexed == INDEXED['full']:
                if self.page_count_uncertain or self.active_stories()\
                       .filter(page_count_uncertain=True).exists():
                    is_indexed = INDEXED['partial']

            if self.is_indexed != is_indexed:
                self.is_indexed = is_indexed
                self.save()
                if self.active_variants():
                    for variant in self.active_variants():
                        variant.is_indexed = is_indexed
                        variant.save()

        index_delta = 0
        if self.series.is_comics_publication:
            if not was_indexed and self.is_indexed:
                index_delta = 1
            elif was_indexed and not self.is_indexed:
                index_delta = -1
        return index_delta

    _update_stats = True

    def stat_counts(self):
        """
        Returns all count values relevant to this issue.

        Includes counts for the issue itself.

        Non-comics publications return statistics only for stories and covers,
        as non-comics issues do not count towards stats.

        Note that we have a special value "series issues", because non-variant
        issues are counted differently with respect to series than in general.
        A series always counts its own non-variant issues, even when the series
        is not a comics publication.
        """
        if self.deleted:
            return {}

        counts = {
            'stories': self.active_stories().count(),
            'covers': self.active_covers().count(),
        }

        if not self.variant_of_id:
            counts['series issues'] = 1

        if self.series.is_comics_publication:
            if self.variant_of_id:
                counts['variant issues'] = 1
            else:
                counts['issues'] = 1
                if self.is_indexed != INDEXED['skeleton']:
                    counts['issue indexes'] = 1
        return counts

    def get_absolute_url(self):
        return urlresolvers.reverse('show_issue', kwargs={'issue_id': self.id})

    @property
    def full_descriptor(self):
        if self.variant_name:
            return "%s [%s]" % (self.issue_descriptor, self.variant_name)
        else:
            return self.issue_descriptor

    @property
    def issue_descriptor(self):
        return issue_descriptor(self)

    @property
    def display_full_descriptor(self):
        number = self.full_descriptor
        if number:
            return u'#' + number
        else:
            return u''

    @property
    def display_number(self):
        number = self.issue_descriptor
        if number:
            return u'#' + number
        else:
            return u''

    def full_name(self, variant_name=True):
        if variant_name and self.variant_name:
            return u'%s %s [%s]' % (self.series.full_name(),
                                    self.display_number, self.variant_name)
        else:
            return u'%s %s' % (self.series.full_name(), self.display_number)

    def full_name_with_link(self, publisher=False):
        name_link = self.series.full_name_with_link(publisher)
        return mark_safe(
            '%s <a href="%s">%s</a>' %
            (name_link, self.get_absolute_url(), esc(self.display_number)))

    def short_name(self):
        if self.variant_name:
            return u'%s %s [%s]' % (self.series.name, self.display_number,
                                    self.variant_name)
        else:
            return u'%s %s' % (self.series.name, self.display_number)

    def __unicode__(self):
        if self.variant_name:
            return u'%s %s [%s]' % (self.series, self.display_number,
                                    self.variant_name)
        else:
            return u'%s %s' % (self.series, self.display_number)
Exemple #7
0
class XForm(XFormMixin, BaseModel):
    CLONED_SUFFIX = '_cloned'
    MAX_ID_LENGTH = 100

    xls = models.FileField(upload_to=upload_to, null=True)
    json = models.TextField(default=u'')
    description = models.TextField(default=u'', null=True, blank=True)
    xml = models.TextField()

    user = models.ForeignKey(User, related_name='xforms', null=True)
    require_auth = models.BooleanField(default=False)
    shared = models.BooleanField(default=False)
    shared_data = models.BooleanField(default=False)
    downloadable = models.BooleanField(default=True)
    allows_sms = models.BooleanField(default=False)
    encrypted = models.BooleanField(default=False)
    deleted_by = models.ForeignKey(User, related_name='xform_deleted_by',
                                   null=True, on_delete=models.SET_NULL,
                                   default=None, blank=True)

    # the following fields are filled in automatically
    sms_id_string = models.SlugField(
        editable=False,
        verbose_name=ugettext_lazy("SMS ID"),
        max_length=MAX_ID_LENGTH,
        default='')
    id_string = models.SlugField(
        editable=False,
        verbose_name=ugettext_lazy("ID"),
        max_length=MAX_ID_LENGTH)
    title = models.CharField(editable=False, max_length=XFORM_TITLE_LENGTH)
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    deleted_at = models.DateTimeField(blank=True, null=True)
    last_submission_time = models.DateTimeField(blank=True, null=True)
    has_start_time = models.BooleanField(default=False)
    uuid = models.CharField(max_length=36, default=u'')

    uuid_regex = re.compile(r'(<instance>.*?id="[^"]+">)(.*</instance>)(.*)',
                            re.DOTALL)
    instance_id_regex = re.compile(r'<instance>.*?id="([^"]+)".*</instance>',
                                   re.DOTALL)
    uuid_node_location = 2
    uuid_bind_location = 4
    bamboo_dataset = models.CharField(max_length=60, default=u'')
    instances_with_geopoints = models.BooleanField(default=False)
    instances_with_osm = models.BooleanField(default=False)
    num_of_submissions = models.IntegerField(default=0)
    version = models.CharField(
        max_length=XFORM_TITLE_LENGTH, null=True, blank=True)
    project = models.ForeignKey('Project')
    created_by = models.ForeignKey(User, null=True, blank=True)
    metadata_set = GenericRelation(
        'main.MetaData',
        content_type_field='content_type_id',
        object_id_field="object_id")
    has_hxl_support = models.BooleanField(default=False)
    last_updated_at = models.DateTimeField(auto_now=True)
    hash = models.CharField(_("Hash"), max_length=36, blank=True, null=True,
                            default=None)
    # XForm was created as a merged dataset
    is_merged_dataset = models.BooleanField(default=False)

    tags = TaggableManager()

    class Meta:
        app_label = 'logger'
        unique_together = (("user", "id_string", "project"),
                           ("user", "sms_id_string", "project"))
        verbose_name = ugettext_lazy("XForm")
        verbose_name_plural = ugettext_lazy("XForms")
        ordering = ("pk", )
        permissions = (
            ("view_xform", _("Can view associated data")),
            ("view_xform_all", _("Can view all associated data")),
            ("view_xform_data", _("Can view submitted data")),
            ("report_xform", _("Can make submissions to the form")),
            ("move_xform", _(u"Can move form between projects")),
            ("transfer_xform", _(u"Can transfer form ownership.")),
            ("can_export_xform_data", _(u"Can export form data")),
            ("delete_submission", _(u"Can delete submissions from form")),
        )

    def file_name(self):
        return self.id_string + ".xml"

    def url(self):
        return reverse(
            "download_xform",
            kwargs={
                "username": self.user.username,
                "id_string": self.id_string
            })

    @property
    def has_instances_with_geopoints(self):
        return self.instances_with_geopoints

    def _set_id_string(self):
        matches = self.instance_id_regex.findall(self.xml)
        if len(matches) != 1:
            raise XLSFormError(_("There should be a single id string."))
        self.id_string = matches[0]

    def _set_title(self):
        xml = re.sub(r"\s+", " ", self.xml)
        matches = title_pattern.findall(xml)

        if len(matches) != 1:
            raise XLSFormError(_("There should be a single title."), matches)

        if matches:
            title_xml = matches[0][:XFORM_TITLE_LENGTH]
        else:
            title_xml = self.title[:XFORM_TITLE_LENGTH] if self.title else ''

        if self.title and title_xml != self.title:
            title_xml = self.title[:XFORM_TITLE_LENGTH]
            if isinstance(self.xml, b):
                self.xml = self.xml.decode('utf-8')
            self.xml = title_pattern.sub(u"<h:title>%s</h:title>" % title_xml,
                                         self.xml)
            self._set_hash()
        if contains_xml_invalid_char(title_xml):
            raise XLSFormError(
                _("Title shouldn't have any invalid xml "
                  "characters ('>' '&' '<')"))

        self.title = title_xml

    def _set_hash(self):
        self.hash = self.get_hash()

    def _set_encrypted_field(self):
        if self.json and self.json != '':
            json_dict = json.loads(self.json)
            if 'submission_url' in json_dict and 'public_key' in json_dict:
                self.encrypted = True
            else:
                self.encrypted = False

    def update(self, *args, **kwargs):
        super(XForm, self).save(*args, **kwargs)

    def save(self, *args, **kwargs):
        update_fields = kwargs.get('update_fields')
        if update_fields:
            kwargs['update_fields'] = list(
                set(list(update_fields) + ['date_modified']))
        if update_fields is None or 'title' in update_fields:
            self._set_title()
        if self.pk is None:
            self._set_hash()
        if update_fields is None or 'encrypted' in update_fields:
            self._set_encrypted_field()
        if update_fields is None or 'id_string' in update_fields:
            old_id_string = self.id_string
            if not self.deleted_at:
                self._set_id_string()
            # check if we have an existing id_string,
            # if so, the one must match but only if xform is NOT new
            if self.pk and old_id_string and old_id_string != self.id_string \
                    and self.num_of_submissions > 0:
                raise XLSFormError(
                    _(u"Your updated form's id_string '%(new_id)s' must match "
                      "the existing forms' id_string '%(old_id)s'." %
                      {'new_id': self.id_string,
                       'old_id': old_id_string}))

            if getattr(settings, 'STRICT', True) and \
                    not re.search(r"^[\w-]+$", self.id_string):
                raise XLSFormError(
                    _(u'In strict mode, the XForm ID must be a '
                      'valid slug and contain no spaces.'))

        if not self.sms_id_string and (update_fields is None or
                                       'id_string' in update_fields):
            try:
                # try to guess the form's wanted sms_id_string
                # from it's json rep (from XLSForm)
                # otherwise, use id_string to ensure uniqueness
                self.sms_id_string = json.loads(self.json).get(
                    'sms_keyword', self.id_string)
            except Exception:
                self.sms_id_string = self.id_string

        if 'skip_xls_read' in kwargs:
            del kwargs['skip_xls_read']

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

    def __str__(self):
        return getattr(self, "id_string", "")

    def soft_delete(self, user=None):
        """
        Return the soft deletion timestamp
        Mark the XForm as soft deleted, appending a timestamped suffix to the
        id_string and sms_id_string to make the initial values available
        without violating the uniqueness constraint.
        Also soft deletes associated dataviews
        """

        soft_deletion_time = timezone.now()
        deletion_suffix = soft_deletion_time.strftime('-deleted-at-%s')
        self.deleted_at = soft_deletion_time
        self.id_string += deletion_suffix
        self.sms_id_string += deletion_suffix
        self.downloadable = False
        update_fields = ['date_modified', 'deleted_at', 'id_string',
                         'sms_id_string', 'downloadable']
        if user is not None:
            self.deleted_by = user
            update_fields.append('deleted_by')

        self.save(update_fields=update_fields)
        for dataview in self.dataview_set.all():
            dataview.soft_delete(user)

    def submission_count(self, force_update=False):
        if self.num_of_submissions == 0 or force_update:
            if self.is_merged_dataset:
                count = self.mergedxform.xforms.aggregate(
                    num=Sum('num_of_submissions')).get('num')
            else:
                count = self.instances.filter(deleted_at__isnull=True).count()

            if count != self.num_of_submissions:
                self.num_of_submissions = count
                self.save(update_fields=['num_of_submissions'])

                # clear cache
                key = '{}{}'.format(XFORM_COUNT, self.pk)
                safe_delete(key)

        return self.num_of_submissions

    submission_count.short_description = ugettext_lazy("Submission Count")

    @property
    def submission_count_for_today(self):
        current_timzone_name = timezone.get_current_timezone_name()
        current_timezone = pytz.timezone(current_timzone_name)
        today = datetime.today()
        current_date = current_timezone.localize(
            datetime(today.year, today.month, today.day))
        count = self.instances.filter(
            deleted_at__isnull=True, date_created=current_date).count()
        return count

    def geocoded_submission_count(self):
        """Number of geocoded submissions."""
        return self.instances.filter(
            deleted_at__isnull=True, geom__isnull=False).count()

    def time_of_last_submission(self):
        if self.last_submission_time is None and self.num_of_submissions > 0:
            try:
                last_submission = self.instances.\
                    filter(deleted_at__isnull=True).latest("date_created")
            except ObjectDoesNotExist:
                pass
            else:
                self.last_submission_time = last_submission.date_created
                self.save()
        return self.last_submission_time

    def time_of_last_submission_update(self):
        try:
            # we also consider deleted instances in this case
            return self.instances.latest("date_modified").date_modified
        except ObjectDoesNotExist:
            pass

    def get_hash(self):
        return u'md5:%s' % md5(self.xml.encode('utf8')).hexdigest()

    @property
    def can_be_replaced(self):
        return self.num_of_submissions == 0

    @classmethod
    def public_forms(cls):
        return cls.objects.filter(shared=True)
Exemple #8
0
class Service(ChangeLoggedModel, CustomFieldModel):
    """
    A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
    optionally be tied to one or more specific IPAddresses belonging to its parent.
    """
    device = models.ForeignKey(to='dcim.Device',
                               on_delete=models.CASCADE,
                               related_name='services',
                               verbose_name='device',
                               null=True,
                               blank=True)
    virtual_machine = models.ForeignKey(to='virtualization.VirtualMachine',
                                        on_delete=models.CASCADE,
                                        related_name='services',
                                        null=True,
                                        blank=True)
    name = models.CharField(max_length=30)
    protocol = models.PositiveSmallIntegerField(choices=IP_PROTOCOL_CHOICES)
    port = models.PositiveIntegerField(
        validators=[MinValueValidator(1),
                    MaxValueValidator(65535)],
        verbose_name='Port number')
    ipaddresses = models.ManyToManyField(to='ipam.IPAddress',
                                         related_name='services',
                                         blank=True,
                                         verbose_name='IP addresses')
    description = models.CharField(max_length=100, blank=True)
    custom_field_values = GenericRelation(to='extras.CustomFieldValue',
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')

    tags = TaggableManager()

    csv_headers = [
        'device', 'virtual_machine', 'name', 'protocol', 'description'
    ]

    class Meta:
        ordering = ['protocol', 'port']

    def __str__(self):
        return '{} ({}/{})'.format(self.name, self.port,
                                   self.get_protocol_display())

    def get_absolute_url(self):
        return reverse('ipam:service', args=[self.pk])

    @property
    def parent(self):
        return self.device or self.virtual_machine

    def clean(self):

        # A Service must belong to a Device *or* to a VirtualMachine
        if self.device and self.virtual_machine:
            raise ValidationError(
                "A service cannot be associated with both a device and a virtual machine."
            )
        if not self.device and not self.virtual_machine:
            raise ValidationError(
                "A service must be associated with either a device or a virtual machine."
            )

    def to_csv(self):
        return (
            self.device.name if self.device else None,
            self.virtual_machine.name if self.virtual_machine else None,
            self.name,
            self.get_protocol_display(),
            self.port,
            self.description,
        )
Exemple #9
0
class Project(models.Model):
    #Auto fields
    pub_date = models.DateTimeField(_('Publication date'), auto_now_add=True)
    modified_date = models.DateTimeField(_('Modified date'), auto_now=True)

    #Generally from conf.py
    users = models.ManyToManyField(User, verbose_name=_('User'),
                                   related_name='projects')
    name = models.CharField(_('Name'), max_length=255)
    slug = models.SlugField(_('Slug'), max_length=255, unique=True)
    description = models.TextField(_('Description'), blank=True,
                                   help_text=_('The reStructuredText '
                                               'description of the project'))
    repo = models.CharField(_('Repository URL'), max_length=100, blank=True,
                            help_text=_('Checkout URL for your code (hg, git, '
                                        'etc.). Ex. http://github.com/'
                                        'ericholscher/django-kong.git'))
    repo_type = models.CharField(_('Repository type'), max_length=10,
                                 choices=constants.REPO_CHOICES, default='git')
    project_url = models.URLField(_('Project URL'), blank=True,
                                  help_text=_('The project\'s homepage'),
                                  verify_exists=False)
    version = models.CharField(_('Version'), max_length=100, blank=True,
                               help_text=_('Project version these docs apply '
                                           'to, i.e. 1.0a'))
    copyright = models.CharField(_('Copyright'), max_length=255, blank=True,
                                 help_text=_('Project copyright information'))
    theme = models.CharField(
        _('Theme'), max_length=20, choices=constants.DEFAULT_THEME_CHOICES,
        default=constants.THEME_DEFAULT,
        help_text=(u'<a href="http://sphinx.pocoo.org/theming.html#builtin-'
                   'themes" target="_blank">%s</a>') % _('Examples'))
    suffix = models.CharField(_('Suffix'), max_length=10, editable=False,
                              default='.rst')
    default_version = models.CharField(
        _('Default version'), max_length=255, default='latest',
        help_text=_('The version of your project that / redirects to'))
    # In default_branch, None max_lengtheans the backend should choose the
    # appropraite branch. Eg 'master' for git
    default_branch = models.CharField(
        _('Default branch'), max_length=255, default=None, null=True,
        blank=True, help_text=_('What branch "latest" points to. Leave empty '
                                'to use the default value for your VCS (eg. '
                                'trunk or master).'))
    requirements_file = models.CharField(
        _('Requirements file'), max_length=255, default=None, null=True,
        blank=True, help_text=_(
            'Requires Virtualenv. A <a '
            'href="http://www.pip-installer.org/en/latest/requirements.html">'
            'pip requirements file</a> needed to build your documentation. '
            'Path from the root of your project.'))
    documentation_type = models.CharField(
        _('Documentation type'), max_length=20,
        choices=constants.DOCUMENTATION_CHOICES, default='sphinx',
        help_text=_('Type of documentation you are building. <a href="http://'
                    'sphinx.pocoo.org/builders.html#sphinx.builders.html.'
                    'DirectoryHTMLBuilder">More info</a>.'))
    analytics_code = models.CharField(
        _('Analytics code'), max_length=50, null=True, blank=True,
        help_text=_("Google Analytics Tracking ID (ex. UA-22345342-1). "
                    "This may slow down your page loads."))

    # Other model data.
    path = models.CharField(_('Path'), max_length=255, editable=False,
                            help_text=_("The directory where conf.py lives"))
    conf_py_file = models.CharField(
        _('Python configuration file'), max_length=255, default='', blank=True,
        help_text=_('Path from project root to conf.py file (ex. docs/conf.py)'
                    '. Leave blank if you want us to find it for you.'))

    featured = models.BooleanField(_('Featured'))
    skip = models.BooleanField(_('Skip'))
    use_virtualenv = models.BooleanField(
        _('Use virtualenv'),
        help_text=_("Install your project inside a virtualenv using setup.py "
                    "install"))

    # This model attribute holds the python interpreter used to create the
    # virtual environment
    python_interpreter = models.CharField(
        _('Python Interpreter'),
        max_length=20,
        choices=constants.PYTHON_CHOICES,
        default='python',
        help_text=_("(Beta) The Python interpreter used to create the virtual "
                    "environment."))

    use_system_packages = models.BooleanField(
        _('Use system packages'),
        help_text=_("Give the virtual environment access to the global "
                    "sites-packages dir"))
    django_packages_url = models.CharField(_('Django Packages URL'),
                                           max_length=255, blank=True)
    crate_url = models.CharField(_('Crate URL'), max_length=255, blank=True)
    privacy_level = models.CharField(
        _('Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES,
        default='public',
        help_text=_("(Beta) Level of privacy that you want on the repository. "
                    "Protected means public but not in listings."))
    version_privacy_level = models.CharField(
        _('Version Privacy Level'), max_length=20,
        choices=constants.PRIVACY_CHOICES, default='public',
        help_text=_("(Beta) Default level of privacy you want on built "
                    "versions of documentation."))

    # Subprojects
    related_projects = models.ManyToManyField(
        'self', verbose_name=_('Related projects'), blank=True, null=True,
        symmetrical=False, through=ProjectRelationship)

    # Language bits
    language = models.CharField('Language', max_length=20, default='en',
                                help_text="The language the project "
                                "documentation is rendered in",
                                choices=constants.LANGUAGES)
    # A subproject pointed at it's main language, so it can be tracked
    main_language_project = models.ForeignKey('self',
                                              related_name='translations',
                                              blank=True, null=True)

    tags = TaggableManager(blank=True)
    objects = ProjectManager()

    class Meta:
        ordering = ('slug',)
        permissions = (
            # Translators: Permission around whether a user can view the
            # project
            ('view_project', _('View Project')),
        )

    def __unicode__(self):
        return self.name

    @property
    def subdomain(self):
        prod_domain = getattr(settings, 'PRODUCTION_DOMAIN')
        subdomain_slug = self.slug.replace('_', '-')
        return "%s.%s" % (subdomain_slug, prod_domain)

    def save(self, *args, **kwargs):
        #if hasattr(self, 'pk'):
            #previous_obj = self.__class__.objects.get(pk=self.pk)
            #if previous_obj.repo != self.repo:
                #Needed to not have an import loop on Project
                #from projects import tasks
                #This needs to run on the build machine.
                #tasks.remove_dir.delay(os.path.join(self.doc_path,
                                                    #'checkouts'))
        if not self.slug:
            self.slug = slugify(self.name)
            if self.slug == '':
                raise Exception(_("Model must have slug"))
        obj = super(Project, self).save(*args, **kwargs)
        for owner in self.users.all():
            assign('view_project', owner, self)
        return obj

    def get_absolute_url(self):
        return reverse('projects_detail', args=[self.slug])

    def get_docs_url(self, version_slug=None, lang_slug=None):
        """
        Return a url for the docs. Always use http for now,
        to avoid content warnings.
        """
        protocol = "http"
        version = version_slug or self.get_default_version()
        use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False)
        if not lang_slug:
            lang_slug = self.language
        if use_subdomain:
            return "%s://%s/%s/%s/" % (
                protocol,
                self.subdomain,
                lang_slug,
                version,
            )
        else:
            return reverse('docs_detail', kwargs={
                'project_slug': self.slug,
                'lang_slug': lang_slug,
                'version_slug': version,
                'filename': ''
            })

    def get_translation_url(self, version_slug=None):
        parent = self.main_language_project
        lang_slug = self.language
        protocol = "http"
        version = version_slug or parent.get_default_version()
        use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False)
        if use_subdomain:
            return "%s://%s/%s/%s/" % (
                protocol,
                parent.subdomain,
                lang_slug,
                version,
            )
        else:
            return reverse('docs_detail', kwargs={
                'project_slug': parent.slug,
                'lang_slug': lang_slug,
                'version_slug': version,
                'filename': ''
            })

    def get_builds_url(self):
        return reverse('builds_project_list', kwargs={
            'project_slug': self.slug,
        })

    def get_pdf_url(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_URL, 'pdf', self.slug, version_slug,
                            '%s.pdf' % self.slug)
        return path

    def get_pdf_path(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_ROOT, 'pdf', self.slug,
                            version_slug, '%s.pdf' % self.slug)
        return path

    def get_epub_url(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_URL, 'epub', self.slug,
                            version_slug, '%s.epub' % self.slug)
        return path

    def get_epub_path(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_ROOT, 'epub', self.slug,
                            version_slug, '%s.epub' % self.slug)
        return path

    def get_manpage_url(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_URL, 'man', self.slug, version_slug,
                            '%s.1' % self.slug)
        return path

    def get_manpage_path(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_ROOT, 'man', self.slug,
                            version_slug, '%s.1' % self.slug)
        return path

    def get_htmlzip_url(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_URL, 'htmlzip', self.slug,
                            version_slug, '%s.zip' % self.slug)
        return path

    def get_htmlzip_path(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_ROOT, 'htmlzip', self.slug,
                            version_slug, '%s.zip' % self.slug)
        return path

    def get_dash_url(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_URL, 'dash', self.slug,
                            version_slug, '%s.tgz' % self.doc_name)
        return path

    def get_dash_path(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_ROOT, 'dash', self.slug,
                            version_slug, '%s.tgz' % self.doc_name)
        return path

    def get_dash_feed_path(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_ROOT, 'dash', self.slug,
                            version_slug, '%s.xml' % self.doc_name)
        return path

    def get_dash_feed_url(self, version_slug='latest'):
        path = os.path.join(settings.MEDIA_URL,
                            'dash',
                            self.slug,
                            version_slug,
                            '%s.xml' % self.doc_name)
        return path

    @property
    def doc_name(self):
        return self.name.replace(' ', '_')

    #Doc PATH:
    #MEDIA_ROOT/slug/checkouts/version/<repo>

    @property
    def doc_path(self):
        return os.path.join(settings.DOCROOT, self.slug)

    def checkout_path(self, version='latest'):
        return os.path.join(self.doc_path, 'checkouts', version)

    def venv_path(self, version='latest'):
        return os.path.join(self.doc_path, 'envs', version)

    def translations_path(self, language=None):
        if not language:
            language = self.language
        return os.path.join(self.doc_path, 'translations', language)

    def venv_bin(self, version='latest', bin='python'):
        return os.path.join(self.venv_path(version), 'bin', bin)

    def full_doc_path(self, version='latest'):
        """
        The path to the documentation root in the project.
        """
        doc_base = self.checkout_path(version)
        for possible_path in ['docs', 'doc', 'Doc']:
            if os.path.exists(os.path.join(doc_base, '%s' % possible_path)):
                return os.path.join(doc_base, '%s' % possible_path)
        #No docs directory, docs are at top-level.
        return doc_base

    def full_build_path(self, version='latest'):
        """
        The path to the build html docs in the project.
        """
        return os.path.join(self.conf_dir(version), "_build", "html")

    def full_latex_path(self, version='latest'):
        """
        The path to the build latex docs in the project.
        """
        return os.path.join(self.conf_dir(version), "_build", "latex")

    def full_man_path(self, version='latest'):
        """
        The path to the build latex docs in the project.
        """
        return os.path.join(self.conf_dir(version), "_build", "man")

    def full_epub_path(self, version='latest'):
        """
        The path to the build latex docs in the project.
        """
        return os.path.join(self.conf_dir(version), "_build", "epub")

    def full_dash_path(self, version='latest'):
        """
        The path to the build dash docs in the project.
        """
        return os.path.join(self.conf_dir(version), "_build", "dash")

    def rtd_build_path(self, version="latest"):
        """
        The path to the build html docs in the project.
        """
        return os.path.join(self.doc_path, 'rtd-builds', version)

    def rtd_cname_path(self, cname):
        """
        The path to the build html docs in the project.
        """
        return os.path.join(settings.CNAME_ROOT, cname)

    def conf_file(self, version='latest'):
        if self.conf_py_file:
            log.debug('Inserting conf.py file path from model')
            return os.path.join(self.checkout_path(version), self.conf_py_file)
        files = self.find('conf.py', version)
        if not files:
            files = self.full_find('conf.py', version)
        if len(files) == 1:
            return files[0]
        elif len(files) > 1:
            for file in files:
                if file.find('doc', 70) != -1:
                    return file
        else:
            raise ProjectImportError(_("Conf File Missing."))

    def conf_dir(self, version='latest'):
        conf_file = self.conf_file(version)
        if conf_file:
            return conf_file.replace('/conf.py', '')

    @property
    def highest_version(self):
        return _highest(self.api_versions())

    @property
    def is_imported(self):
        return bool(self.repo)

    @property
    def has_good_build(self):
        return self.builds.filter(success=True).exists()

    @property
    def has_versions(self):
        return self.versions.exists()

    @property
    def has_aliases(self):
        return self.aliases.exists()

    def has_pdf(self, version_slug='latest'):
        return os.path.exists(self.get_pdf_path(version_slug))

    def has_manpage(self, version_slug='latest'):
        return os.path.exists(self.get_manpage_path(version_slug))

    def has_epub(self, version_slug='latest'):
        return os.path.exists(self.get_epub_path(version_slug))

    def has_dash(self, version_slug='latest'):
        return os.path.exists(self.get_dash_path(version_slug))

    def has_htmlzip(self, version_slug='latest'):
        return os.path.exists(self.get_htmlzip_path(version_slug))

    @property
    def sponsored(self):
        return False

    def vcs_repo(self, version='latest'):
        #if hasattr(self, '_vcs_repo'):
            #return self._vcs_repo
        backend = backend_cls.get(self.repo_type)
        if not backend:
            repo = None
        else:
            proj = VCSProject(self.name, self.default_branch,
                              self.checkout_path(version), self.repo)
            repo = backend(proj, version)
        #self._vcs_repo = repo
        return repo

    @property
    def contribution_backend(self):
        if hasattr(self, '_contribution_backend'):
            return self._contribution_backend
        if not self.vcs_repo:
            cb = None
        else:
            cb = self.vcs_repo.get_contribution_backend()
        self._contribution_backend = cb
        return cb

    def repo_lock(self, timeout=5, polling_interval=0.2):
        return Lock(self, timeout, polling_interval)

    def find(self, file, version):
        """
        A balla API to find files inside of a projects dir.
        """
        matches = []
        for root, dirnames, filenames in os.walk(self.full_doc_path(version)):
            for filename in fnmatch.filter(filenames, file):
                matches.append(os.path.join(root, filename))
        return matches

    def full_find(self, file, version):
        """
        A balla API to find files inside of a projects dir.
        """
        matches = []
        for root, dirnames, filenames in os.walk(self.checkout_path(version)):
            for filename in fnmatch.filter(filenames, file):
                matches.append(os.path.join(root, filename))
        return matches

    def get_latest_build(self):
        try:
            return self.builds.filter(type='html')[0]
        except IndexError:
            return None

    def api_versions(self):
        ret = []
        for version_data in api.version.get(project=self.pk,
                                            active=True)['objects']:
            version = make_api_version(version_data)
            ret.append(version)
        return sort_version_aware(ret)

    def active_versions(self):
        return (self.versions.filter(built=True, active=True) |
                self.versions.filter(active=True, uploaded=True))

    def ordered_active_versions(self):
        return sort_version_aware(self.versions.filter(active=True))

    def all_active_versions(self):
        """A temporary workaround for active_versions filtering out things
        that were active, but failed to build

        """
        return self.versions.filter(active=True)

    def version_from_branch_name(self, branch):
        try:
            return (self.versions.filter(identifier=branch) |
                    self.versions.filter(identifier=('remotes/origin/%s'
                                                     % branch)))[0]
        except IndexError:
            return None

    def get_default_version(self):
        """
        Get the default version (slug).

        Returns self.default_version if the version with that slug actually
        exists (is built and published). Otherwise returns 'latest'.
        """
        # latest is a special case where we don't have to check if it exists
        if self.default_version == 'latest':
            return self.default_version
        # check if the default_version exists
        version_qs = self.versions.filter(
            slug=self.default_version, active=True
        )
        if version_qs.exists():
            return self.default_version
        return 'latest'

    def get_default_branch(self):
        """
        Get the version representing "latest"
        """
        if self.default_branch:
            return self.default_branch
        else:
            return self.vcs_repo().fallback_branch

    def add_subproject(self, child):
        subproject, created = ProjectRelationship.objects.get_or_create(
            parent=self, child=child,
        )
        return subproject

    def remove_subproject(self, child):
        ProjectRelationship.objects.filter(parent=self, child=child).delete()
        return
Exemple #10
0
class IPAddress(ChangeLoggedModel, CustomFieldModel):
    """
    An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
    configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like
    Prefixes, IPAddresses can optionally be assigned to a VRF. An IPAddress can optionally be assigned to an Interface.
    Interfaces can have zero or more IPAddresses assigned to them.

    An IPAddress can also optionally point to a NAT inside IP, designating itself as a NAT outside IP. This is useful,
    for example, when mapping public addresses to private addresses. When an Interface has been assigned an IPAddress
    which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
    """
    family = models.PositiveSmallIntegerField(choices=AF_CHOICES,
                                              editable=False)
    address = IPAddressField(help_text='IPv4 or IPv6 address (with mask)')
    vrf = models.ForeignKey(to='ipam.VRF',
                            on_delete=models.PROTECT,
                            related_name='ip_addresses',
                            blank=True,
                            null=True,
                            verbose_name='VRF')
    tenant = models.ForeignKey(to='tenancy.Tenant',
                               on_delete=models.PROTECT,
                               related_name='ip_addresses',
                               blank=True,
                               null=True)
    status = models.PositiveSmallIntegerField(
        choices=IPADDRESS_STATUS_CHOICES,
        default=IPADDRESS_STATUS_ACTIVE,
        verbose_name='Status',
        help_text='The operational status of this IP')
    role = models.PositiveSmallIntegerField(
        verbose_name='Role',
        choices=IPADDRESS_ROLE_CHOICES,
        blank=True,
        null=True,
        help_text='The functional role of this IP')
    interface = models.ForeignKey(to='dcim.Interface',
                                  on_delete=models.CASCADE,
                                  related_name='ip_addresses',
                                  blank=True,
                                  null=True)
    nat_inside = models.OneToOneField(
        to='self',
        on_delete=models.SET_NULL,
        related_name='nat_outside',
        blank=True,
        null=True,
        verbose_name='NAT (Inside)',
        help_text='The IP for which this address is the "outside" IP')
    description = models.CharField(max_length=100, blank=True)
    custom_field_values = GenericRelation(to='extras.CustomFieldValue',
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')

    objects = IPAddressManager()
    tags = TaggableManager()

    csv_headers = [
        'address',
        'vrf',
        'tenant',
        'status',
        'role',
        'device',
        'virtual_machine',
        'interface_name',
        'is_primary',
        'description',
    ]

    class Meta:
        ordering = ['family', 'address']
        verbose_name = 'IP address'
        verbose_name_plural = 'IP addresses'

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

    def get_absolute_url(self):
        return reverse('ipam:ipaddress', args=[self.pk])

    def get_duplicates(self):
        return IPAddress.objects.filter(
            vrf=self.vrf,
            address__net_host=str(self.address.ip)).exclude(pk=self.pk)

    def clean(self):

        if self.address:

            # Enforce unique IP space (if applicable)
            if self.role not in IPADDRESS_ROLES_NONUNIQUE and (
                (self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE) or
                (self.vrf and self.vrf.enforce_unique)):
                duplicate_ips = self.get_duplicates()
                if duplicate_ips:
                    raise ValidationError({
                        'address':
                        "Duplicate IP address found in {}: {}".format(
                            "VRF {}".format(self.vrf)
                            if self.vrf else "global table",
                            duplicate_ips.first(),
                        )
                    })

    def save(self, *args, **kwargs):
        if self.address:
            # Infer address family from IPAddress object
            self.family = self.address.version
        super(IPAddress, self).save(*args, **kwargs)

    def to_csv(self):

        # Determine if this IP is primary for a Device
        if self.family == 4 and getattr(self, 'primary_ip4_for', False):
            is_primary = True
        elif self.family == 6 and getattr(self, 'primary_ip6_for', False):
            is_primary = True
        else:
            is_primary = False

        return (
            self.address,
            self.vrf.rd if self.vrf else None,
            self.tenant.name if self.tenant else None,
            self.get_status_display(),
            self.get_role_display(),
            self.device.identifier if self.device else None,
            self.virtual_machine.name if self.virtual_machine else None,
            self.interface.name if self.interface else None,
            is_primary,
            self.description,
        )

    @property
    def device(self):
        if self.interface:
            return self.interface.device
        return None

    @property
    def virtual_machine(self):
        if self.interface:
            return self.interface.virtual_machine
        return None

    def get_status_class(self):
        return STATUS_CHOICE_CLASSES[self.status]

    def get_role_class(self):
        return ROLE_CHOICE_CLASSES[self.role]
Exemple #11
0
class VLAN(ChangeLoggedModel, CustomFieldModel):
    """
    A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
    to a Site, however VLAN IDs need not be unique within a Site. A VLAN may optionally be assigned to a VLANGroup,
    within which all VLAN IDs and names but be unique.

    Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero
    or more Prefixes assigned to it.
    """
    site = models.ForeignKey(to='dcim.Site',
                             on_delete=models.PROTECT,
                             related_name='vlans',
                             blank=True,
                             null=True)
    group = models.ForeignKey(to='ipam.VLANGroup',
                              on_delete=models.PROTECT,
                              related_name='vlans',
                              blank=True,
                              null=True)
    vid = models.PositiveSmallIntegerField(
        verbose_name='ID',
        validators=[MinValueValidator(1),
                    MaxValueValidator(4094)])
    name = models.CharField(max_length=64)
    tenant = models.ForeignKey(to='tenancy.Tenant',
                               on_delete=models.PROTECT,
                               related_name='vlans',
                               blank=True,
                               null=True)
    status = models.PositiveSmallIntegerField(choices=VLAN_STATUS_CHOICES,
                                              default=1,
                                              verbose_name='Status')
    role = models.ForeignKey(to='ipam.Role',
                             on_delete=models.SET_NULL,
                             related_name='vlans',
                             blank=True,
                             null=True)
    description = models.CharField(max_length=100, blank=True)
    custom_field_values = GenericRelation(to='extras.CustomFieldValue',
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')

    tags = TaggableManager()

    csv_headers = [
        'site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role',
        'description'
    ]

    class Meta:
        ordering = ['site', 'group', 'vid']
        unique_together = [
            ['group', 'vid'],
            ['group', 'name'],
        ]
        verbose_name = 'VLAN'
        verbose_name_plural = 'VLANs'

    def __str__(self):
        return self.display_name or super(VLAN, self).__str__()

    def get_absolute_url(self):
        return reverse('ipam:vlan', args=[self.pk])

    def clean(self):

        # Validate VLAN group
        if self.group and self.group.site != self.site:
            raise ValidationError({
                'group':
                "VLAN group must belong to the assigned site ({}).".format(
                    self.site)
            })

    def to_csv(self):
        return (
            self.site.name if self.site else None,
            self.group.name if self.group else None,
            self.vid,
            self.name,
            self.tenant.name if self.tenant else None,
            self.get_status_display(),
            self.role.name if self.role else None,
            self.description,
        )

    @property
    def display_name(self):
        if self.vid and self.name:
            return "{} ({})".format(self.vid, self.name)
        return None

    def get_status_class(self):
        return STATUS_CHOICE_CLASSES[self.status]

    def get_members(self):
        # Return all interfaces assigned to this VLAN
        return Interface.objects.filter(
            Q(untagged_vlan_id=self.pk) | Q(tagged_vlans=self.pk))
Exemple #12
0
class Prefix(ChangeLoggedModel, CustomFieldModel):
    """
    A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
    VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be
    assigned to a VLAN where appropriate.
    """
    family = models.PositiveSmallIntegerField(choices=AF_CHOICES,
                                              editable=False)
    prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask')
    site = models.ForeignKey(to='dcim.Site',
                             on_delete=models.PROTECT,
                             related_name='prefixes',
                             blank=True,
                             null=True)
    vrf = models.ForeignKey(to='ipam.VRF',
                            on_delete=models.PROTECT,
                            related_name='prefixes',
                            blank=True,
                            null=True,
                            verbose_name='VRF')
    tenant = models.ForeignKey(to='tenancy.Tenant',
                               on_delete=models.PROTECT,
                               related_name='prefixes',
                               blank=True,
                               null=True)
    vlan = models.ForeignKey(to='ipam.VLAN',
                             on_delete=models.PROTECT,
                             related_name='prefixes',
                             blank=True,
                             null=True,
                             verbose_name='VLAN')
    status = models.PositiveSmallIntegerField(
        choices=PREFIX_STATUS_CHOICES,
        default=PREFIX_STATUS_ACTIVE,
        verbose_name='Status',
        help_text='Operational status of this prefix')
    role = models.ForeignKey(to='ipam.Role',
                             on_delete=models.SET_NULL,
                             related_name='prefixes',
                             blank=True,
                             null=True,
                             help_text='The primary function of this prefix')
    is_pool = models.BooleanField(
        verbose_name='Is a pool',
        default=False,
        help_text='All IP addresses within this prefix are considered usable')
    description = models.CharField(max_length=100, blank=True)
    custom_field_values = GenericRelation(to='extras.CustomFieldValue',
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')

    objects = PrefixQuerySet.as_manager()
    tags = TaggableManager()

    csv_headers = [
        'prefix',
        'vrf',
        'tenant',
        'site',
        'vlan_group',
        'vlan_vid',
        'status',
        'role',
        'is_pool',
        'description',
    ]

    class Meta:
        ordering = ['vrf', 'family', 'prefix']
        verbose_name_plural = 'prefixes'

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

    def get_absolute_url(self):
        return reverse('ipam:prefix', args=[self.pk])

    def clean(self):

        if self.prefix:

            # Disallow host masks
            if self.prefix.version == 4 and self.prefix.prefixlen == 32:
                raise ValidationError({
                    'prefix':
                    "Cannot create host addresses (/32) as prefixes. Create an IPv4 address instead."
                })
            elif self.prefix.version == 6 and self.prefix.prefixlen == 128:
                raise ValidationError({
                    'prefix':
                    "Cannot create host addresses (/128) as prefixes. Create an IPv6 address instead."
                })

            # Enforce unique IP space (if applicable)
            if (self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE) or (
                    self.vrf and self.vrf.enforce_unique):
                duplicate_prefixes = self.get_duplicates()
                if duplicate_prefixes:
                    raise ValidationError({
                        'prefix':
                        "Duplicate prefix found in {}: {}".format(
                            "VRF {}".format(self.vrf)
                            if self.vrf else "global table",
                            duplicate_prefixes.first(),
                        )
                    })

    def save(self, *args, **kwargs):
        if self.prefix:
            # Clear host bits from prefix
            self.prefix = self.prefix.cidr
            # Infer address family from IPNetwork object
            self.family = self.prefix.version
        super(Prefix, self).save(*args, **kwargs)

    def to_csv(self):
        return (
            self.prefix,
            self.vrf.rd if self.vrf else None,
            self.tenant.name if self.tenant else None,
            self.site.name if self.site else None,
            self.vlan.group.name if self.vlan and self.vlan.group else None,
            self.vlan.vid if self.vlan else None,
            self.get_status_display(),
            self.role.name if self.role else None,
            self.is_pool,
            self.description,
        )

    def get_status_class(self):
        return STATUS_CHOICE_CLASSES[self.status]

    def get_duplicates(self):
        return Prefix.objects.filter(vrf=self.vrf, prefix=str(
            self.prefix)).exclude(pk=self.pk)

    def get_child_prefixes(self):
        """
        Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child
        Prefixes belonging to any VRF.
        """
        if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER:
            return Prefix.objects.filter(
                prefix__net_contained=str(self.prefix))
        else:
            return Prefix.objects.filter(prefix__net_contained=str(
                self.prefix),
                                         vrf=self.vrf)

    def get_child_ips(self):
        """
        Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
        child IPAddresses belonging to any VRF.
        """
        if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER:
            return IPAddress.objects.filter(
                address__net_host_contained=str(self.prefix))
        else:
            return IPAddress.objects.filter(address__net_host_contained=str(
                self.prefix),
                                            vrf=self.vrf)

    def get_available_prefixes(self):
        """
        Return all available Prefixes within this prefix as an IPSet.
        """
        prefix = netaddr.IPSet(self.prefix)
        child_prefixes = netaddr.IPSet(
            [child.prefix for child in self.get_child_prefixes()])
        available_prefixes = prefix - child_prefixes

        return available_prefixes

    def get_available_ips(self):
        """
        Return all available IPs within this prefix as an IPSet.
        """
        prefix = netaddr.IPSet(self.prefix)
        child_ips = netaddr.IPSet(
            [ip.address.ip for ip in self.get_child_ips()])
        available_ips = prefix - child_ips

        # Remove unusable IPs from non-pool prefixes
        if not self.is_pool:
            available_ips -= netaddr.IPSet([
                netaddr.IPAddress(self.prefix.first),
                netaddr.IPAddress(self.prefix.last),
            ])

        return available_ips

    def get_first_available_prefix(self):
        """
        Return the first available child prefix within the prefix (or None).
        """
        available_prefixes = self.get_available_prefixes()
        if not available_prefixes:
            return None
        return available_prefixes.iter_cidrs()[0]

    def get_first_available_ip(self):
        """
        Return the first available IP within the prefix (or None).
        """
        available_ips = self.get_available_ips()
        if not available_ips:
            return None
        return '{}/{}'.format(next(available_ips.__iter__()),
                              self.prefix.prefixlen)

    def get_utilization(self):
        """
        Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of
        "container", calculate utilization based on child prefixes. For all others, count child IP addresses.
        """
        if self.status == PREFIX_STATUS_CONTAINER:
            queryset = Prefix.objects.filter(prefix__net_contained=str(
                self.prefix),
                                             vrf=self.vrf)
            child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
            return int(float(child_prefixes.size) / self.prefix.size * 100)
        else:
            # Compile an IPSet to avoid counting duplicate IPs
            child_count = netaddr.IPSet(
                [ip.address.ip for ip in self.get_child_ips()]).size
            prefix_size = self.prefix.size
            if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
                prefix_size -= 2
            return int(float(child_count) / prefix_size * 100)
Exemple #13
0
class Aggregate(ChangeLoggedModel, CustomFieldModel):
    """
    An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
    the hierarchy and track the overall utilization of available address space. Each Aggregate is assigned to a RIR.
    """
    family = models.PositiveSmallIntegerField(choices=AF_CHOICES)
    prefix = IPNetworkField()
    rir = models.ForeignKey(to='ipam.RIR',
                            on_delete=models.PROTECT,
                            related_name='aggregates',
                            verbose_name='RIR')
    date_added = models.DateField(blank=True, null=True)
    description = models.CharField(max_length=100, blank=True)
    custom_field_values = GenericRelation(to='extras.CustomFieldValue',
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')

    tags = TaggableManager()

    csv_headers = ['prefix', 'rir', 'date_added', 'description']

    class Meta:
        ordering = ['family', 'prefix']

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

    def get_absolute_url(self):
        return reverse('ipam:aggregate', args=[self.pk])

    def clean(self):

        if self.prefix:

            # Clear host bits from prefix
            self.prefix = self.prefix.cidr

            # Ensure that the aggregate being added is not covered by an existing aggregate
            covering_aggregates = Aggregate.objects.filter(
                prefix__net_contains_or_equals=str(self.prefix))
            if self.pk:
                covering_aggregates = covering_aggregates.exclude(pk=self.pk)
            if covering_aggregates:
                raise ValidationError({
                    'prefix':
                    "Aggregates cannot overlap. {} is already covered by an existing aggregate ({})."
                    .format(self.prefix, covering_aggregates[0])
                })

            # Ensure that the aggregate being added does not cover an existing aggregate
            covered_aggregates = Aggregate.objects.filter(
                prefix__net_contained=str(self.prefix))
            if self.pk:
                covered_aggregates = covered_aggregates.exclude(pk=self.pk)
            if covered_aggregates:
                raise ValidationError({
                    'prefix':
                    "Aggregates cannot overlap. {} covers an existing aggregate ({})."
                    .format(self.prefix, covered_aggregates[0])
                })

    def save(self, *args, **kwargs):
        if self.prefix:
            # Infer address family from IPNetwork object
            self.family = self.prefix.version
        super(Aggregate, self).save(*args, **kwargs)

    def to_csv(self):
        return (
            self.prefix,
            self.rir.name,
            self.date_added,
            self.description,
        )

    def get_utilization(self):
        """
        Determine the prefix utilization of the aggregate and return it as a percentage.
        """
        queryset = Prefix.objects.filter(
            prefix__net_contained_or_equal=str(self.prefix))
        child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
        return int(float(child_prefixes.size) / self.prefix.size * 100)
Exemple #14
0
class Article(CachingMixin, models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    is_live = models.BooleanField('Display on site', default=True)
    show_in_lists = models.BooleanField('Show in lists', default=True)
    allow_comments = models.BooleanField('Allow comments', default=True)
    title = models.CharField(max_length=128)
    slug = models.SlugField(unique=True)
    pubdate = models.DateTimeField(default=datetime.now)
    subhead = models.CharField(max_length=128)
    authors = models.ManyToManyField(Person,
                                     blank=True,
                                     related_name='article_authors')
    image = ImageField(
        upload_to='img/uploads/article_images',
        help_text='Resized to fit 100% column width in template',
        blank=True,
        null=True)
    image_caption = models.TextField(blank=True)
    image_credit = models.CharField(
        max_length=128,
        blank=True,
        help_text=
        'Optional. Will be appended to end of caption in parens. Accepts HTML.'
    )
    body = models.TextField()
    summary = models.TextField()
    article_type = models.CharField(max_length=32, blank=True)
    category = models.ForeignKey('Category', on_delete=models.CASCADE)
    people = models.ManyToManyField(Person, blank=True)
    organizations = models.ManyToManyField(Organization, blank=True)
    code = models.ManyToManyField(Code, blank=True)
    tags = TaggableManager(
        blank=True,
        help_text=
        'Automatic combined list of Technology Tags and Concept Tags, for easy searching'
    )
    technology_tags = TaggableManager(
        verbose_name='Technology Tags',
        help_text=
        'A comma-separated list of tags describing relevant technologies',
        through=TechnologyTaggedItem,
        blank=True)
    concept_tags = TaggableManager(
        verbose_name='Concept Tags',
        help_text='A comma-separated list of tags describing relevant concepts',
        through=ConceptTaggedItem,
        blank=True)
    objects = models.Manager()
    live_objects = LiveArticleManager()
    disable_auto_linebreaks = models.BooleanField(
        default=False,
        help_text=
        'Check this if body and article blocks already have HTML paragraph tags.'
    )
    article_js_header = models.TextField(blank=True)
    article_js_footer = models.TextField(blank=True)

    class Meta:
        ordering = (
            '-pubdate',
            'title',
        )

    def __str__(self):
        return '%s' % self.title

    @models.permalink
    def get_absolute_url(self):
        return ('article_detail', (), {
            'section': self.section.slug,
            'slug': self.slug
        })

    @property
    def section(self):
        '''follow article category through to section'''
        if self.category:
            return self.category.section
        return None

    @property
    def pretty_pubdate(self):
        '''pre-process for simpler template logic'''
        return dj_date(self.pubdate, "F j, Y")

    @property
    def pretty_caption(self):
        '''pre-process for simpler template logic'''
        _caption = self.image_caption or ''
        _credit = self.image_credit
        if _credit:
            _caption = '%s (%s)' % (_caption, _credit)
        return _caption

    @property
    def pretty_body_text(self):
        '''pre-process for simpler template logic'''
        _body = self.body
        if not self.disable_auto_linebreaks:
            # allow admin users to provide text
            # that already contains <p> tags
            _body = linebreaks(_body)
        return _body

    @property
    def safe_summary(self):
        '''suitable for use in places that must avoid nested anchor tags'''
        return removetags(self.summary, 'a')

    @property
    def merged_tag_list(self):
        '''return a combined list of technology_tags and concept_tags'''
        return [
            item for item in itertools.chain(self.technology_tags.all(),
                                             self.concept_tags.all())
        ]

    def get_live_organization_set(self):
        return self.organizations.filter(is_live=True)

    def get_live_person_set(self):
        return self.people.filter(is_live=True)

    def get_live_author_set(self):
        return self.authors.filter(is_live=True)

    def get_live_code_set(self):
        return self.code.filter(is_live=True)

    def get_live_guide_set(self):
        return self.guidearticle_set.filter(guide__is_live=True,
                                            guide__show_in_lists=True,
                                            guide__pubdate__lte=datetime.now())

    def get_live_author_bio_set(self):
        # only authors with acutal bio information
        author_set = self.get_live_author_set().exclude(description='')
        # filter out bio boxes for Erin, Erika,
        authors_to_exclude = ['erin-kissane', 'erika-owens', 'kio-stark']
        author_set = author_set.exclude(slug__in=authors_to_exclude)
        return author_set
Exemple #15
0
class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
    """
    A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
    DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.

    Each Device must be assigned to a site, and optionally to a rack within that site. Associating a device with a
    particular rack face or unit is optional (for example, vertically mounted PDUs do not consume rack units).

    When a new Device is created, console/power/interface/device bay components are created along with it as dictated
    by the component templates assigned to its DeviceType. Components can also be added, modified, or deleted after the
    creation of a Device.
    """
    device_type = models.ForeignKey(to='dcim.DeviceType',
                                    on_delete=models.PROTECT,
                                    related_name='instances')
    device_role = models.ForeignKey(to='dcim.DeviceRole',
                                    on_delete=models.PROTECT,
                                    related_name='devices')
    tenant = models.ForeignKey(to='tenancy.Tenant',
                               on_delete=models.PROTECT,
                               related_name='devices',
                               blank=True,
                               null=True)
    platform = models.ForeignKey(to='dcim.Platform',
                                 on_delete=models.SET_NULL,
                                 related_name='devices',
                                 blank=True,
                                 null=True)
    name = models.CharField(max_length=64, blank=True, null=True)
    _name = NaturalOrderingField(target_field='name',
                                 max_length=100,
                                 blank=True,
                                 null=True)
    serial = models.CharField(max_length=50,
                              blank=True,
                              verbose_name='Serial number')
    asset_tag = models.CharField(
        max_length=50,
        blank=True,
        null=True,
        unique=True,
        verbose_name='Asset tag',
        help_text='A unique tag used to identify this device')
    site = models.ForeignKey(to='dcim.Site',
                             on_delete=models.PROTECT,
                             related_name='devices')
    rack = models.ForeignKey(to='dcim.Rack',
                             on_delete=models.PROTECT,
                             related_name='devices',
                             blank=True,
                             null=True)
    position = models.PositiveSmallIntegerField(
        blank=True,
        null=True,
        validators=[MinValueValidator(1)],
        verbose_name='Position (U)',
        help_text='The lowest-numbered unit occupied by the device')
    face = models.CharField(max_length=50,
                            blank=True,
                            choices=DeviceFaceChoices,
                            verbose_name='Rack face')
    status = models.CharField(max_length=50,
                              choices=DeviceStatusChoices,
                              default=DeviceStatusChoices.STATUS_ACTIVE)
    primary_ip4 = models.OneToOneField(to='ipam.IPAddress',
                                       on_delete=models.SET_NULL,
                                       related_name='primary_ip4_for',
                                       blank=True,
                                       null=True,
                                       verbose_name='Primary IPv4')
    primary_ip6 = models.OneToOneField(to='ipam.IPAddress',
                                       on_delete=models.SET_NULL,
                                       related_name='primary_ip6_for',
                                       blank=True,
                                       null=True,
                                       verbose_name='Primary IPv6')
    cluster = models.ForeignKey(to='virtualization.Cluster',
                                on_delete=models.SET_NULL,
                                related_name='devices',
                                blank=True,
                                null=True)
    virtual_chassis = models.ForeignKey(to='VirtualChassis',
                                        on_delete=models.SET_NULL,
                                        related_name='members',
                                        blank=True,
                                        null=True)
    vc_position = models.PositiveSmallIntegerField(
        blank=True, null=True, validators=[MaxValueValidator(255)])
    vc_priority = models.PositiveSmallIntegerField(
        blank=True, null=True, validators=[MaxValueValidator(255)])
    comments = models.TextField(blank=True)
    custom_field_values = GenericRelation(to='extras.CustomFieldValue',
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')
    images = GenericRelation(to='extras.ImageAttachment')
    tags = TaggableManager(through=TaggedItem)

    objects = RestrictedQuerySet.as_manager()

    csv_headers = [
        'name',
        'device_role',
        'tenant',
        'manufacturer',
        'device_type',
        'platform',
        'serial',
        'asset_tag',
        'status',
        'site',
        'rack_group',
        'rack_name',
        'position',
        'face',
        'comments',
    ]
    clone_fields = [
        'device_type',
        'device_role',
        'tenant',
        'platform',
        'site',
        'rack',
        'status',
        'cluster',
    ]

    STATUS_CLASS_MAP = {
        DeviceStatusChoices.STATUS_OFFLINE: 'warning',
        DeviceStatusChoices.STATUS_ACTIVE: 'success',
        DeviceStatusChoices.STATUS_PLANNED: 'info',
        DeviceStatusChoices.STATUS_STAGED: 'primary',
        DeviceStatusChoices.STATUS_FAILED: 'danger',
        DeviceStatusChoices.STATUS_INVENTORY: 'default',
        DeviceStatusChoices.STATUS_DECOMMISSIONING: 'warning',
    }

    class Meta:
        ordering = ('_name', 'pk')  # Name may be null
        unique_together = (
            ('site', 'tenant', 'name'),  # See validate_unique below
            ('rack', 'position', 'face'),
            ('virtual_chassis', 'vc_position'),
        )

    def __str__(self):
        return self.display_name or super().__str__()

    def get_absolute_url(self):
        return reverse('dcim:device', args=[self.pk])

    def validate_unique(self, exclude=None):

        # Check for a duplicate name on a device assigned to the same Site and no Tenant. This is necessary
        # because Django does not consider two NULL fields to be equal, and thus will not trigger a violation
        # of the uniqueness constraint without manual intervention.
        if self.name and self.tenant is None:
            if Device.objects.exclude(pk=self.pk).filter(name=self.name,
                                                         site=self.site,
                                                         tenant__isnull=True):
                raise ValidationError(
                    {'name': 'A device with this name already exists.'})

        super().validate_unique(exclude)

    def clean(self):

        super().clean()

        # Validate site/rack combination
        if self.rack and self.site != self.rack.site:
            raise ValidationError({
                'rack':
                "Rack {} does not belong to site {}.".format(
                    self.rack, self.site),
            })

        if self.rack is None:
            if self.face:
                raise ValidationError({
                    'face':
                    "Cannot select a rack face without assigning a rack.",
                })
            if self.position:
                raise ValidationError({
                    'face':
                    "Cannot select a rack position without assigning a rack.",
                })

        # Validate position/face combination
        if self.position and not self.face:
            raise ValidationError({
                'face':
                "Must specify rack face when defining rack position.",
            })

        # Prevent 0U devices from being assigned to a specific position
        if self.position and self.device_type.u_height == 0:
            raise ValidationError({
                'position':
                "A U0 device type ({}) cannot be assigned to a rack position.".
                format(self.device_type)
            })

        if self.rack:

            try:
                # Child devices cannot be assigned to a rack face/unit
                if self.device_type.is_child_device and self.face:
                    raise ValidationError({
                        'face':
                        "Child device types cannot be assigned to a rack face. This is an attribute of the "
                        "parent device."
                    })
                if self.device_type.is_child_device and self.position:
                    raise ValidationError({
                        'position':
                        "Child device types cannot be assigned to a rack position. This is an attribute of "
                        "the parent device."
                    })

                # Validate rack space
                rack_face = self.face if not self.device_type.is_full_depth else None
                exclude_list = [self.pk] if self.pk else []
                available_units = self.rack.get_available_units(
                    u_height=self.device_type.u_height,
                    rack_face=rack_face,
                    exclude=exclude_list)
                if self.position and self.position not in available_units:
                    raise ValidationError({
                        'position':
                        "U{} is already occupied or does not have sufficient space to accommodate a(n) "
                        "{} ({}U).".format(self.position, self.device_type,
                                           self.device_type.u_height)
                    })

            except DeviceType.DoesNotExist:
                pass

        # Validate primary IP addresses
        vc_interfaces = self.vc_interfaces.all()
        if self.primary_ip4:
            if self.primary_ip4.family != 4:
                raise ValidationError({
                    'primary_ip4':
                    f"{self.primary_ip4} is not an IPv4 address."
                })
            if self.primary_ip4.assigned_object in vc_interfaces:
                pass
            elif self.primary_ip4.nat_inside is not None and self.primary_ip4.nat_inside.assigned_object in vc_interfaces:
                pass
            else:
                raise ValidationError({
                    'primary_ip4':
                    f"The specified IP address ({self.primary_ip4}) is not assigned to this device."
                })
        if self.primary_ip6:
            if self.primary_ip6.family != 6:
                raise ValidationError({
                    'primary_ip6':
                    f"{self.primary_ip6} is not an IPv6 address."
                })
            if self.primary_ip6.assigned_object in vc_interfaces:
                pass
            elif self.primary_ip6.nat_inside is not None and self.primary_ip6.nat_inside.assigned_object in vc_interfaces:
                pass
            else:
                raise ValidationError({
                    'primary_ip6':
                    f"The specified IP address ({self.primary_ip6}) is not assigned to this device."
                })

        # Validate manufacturer/platform
        if hasattr(self, 'device_type') and self.platform:
            if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:
                raise ValidationError({
                    'platform':
                    "The assigned platform is limited to {} device types, but this device's type belongs "
                    "to {}.".format(self.platform.manufacturer,
                                    self.device_type.manufacturer)
                })

        # A Device can only be assigned to a Cluster in the same Site (or no Site)
        if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
            raise ValidationError({
                'cluster':
                "The assigned cluster belongs to a different site ({})".format(
                    self.cluster.site)
            })

        # Validate virtual chassis assignment
        if self.virtual_chassis and self.vc_position is None:
            raise ValidationError({
                'vc_position':
                "A device assigned to a virtual chassis must have its position defined."
            })

    def save(self, *args, **kwargs):

        is_new = not bool(self.pk)

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

        # If this is a new Device, instantiate all of the related components per the DeviceType definition
        if is_new:
            ConsolePort.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.consoleporttemplates.all()
            ])
            ConsoleServerPort.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.consoleserverporttemplates.all()
            ])
            PowerPort.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.powerporttemplates.all()
            ])
            PowerOutlet.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.poweroutlettemplates.all()
            ])
            Interface.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.interfacetemplates.all()
            ])
            RearPort.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.rearporttemplates.all()
            ])
            FrontPort.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.frontporttemplates.all()
            ])
            DeviceBay.objects.bulk_create([
                x.instantiate(self)
                for x in self.device_type.devicebaytemplates.all()
            ])

        # Update Site and Rack assignment for any child Devices
        devices = Device.objects.filter(parent_bay__device=self)
        for device in devices:
            device.site = self.site
            device.rack = self.rack
            device.save()

    def to_csv(self):
        return (
            self.name or '',
            self.device_role.name,
            self.tenant.name if self.tenant else None,
            self.device_type.manufacturer.name,
            self.device_type.model,
            self.platform.name if self.platform else None,
            self.serial,
            self.asset_tag,
            self.get_status_display(),
            self.site.name,
            self.rack.group.name if self.rack and self.rack.group else None,
            self.rack.name if self.rack else None,
            self.position,
            self.get_face_display(),
            self.comments,
        )

    @property
    def display_name(self):
        if self.name:
            return self.name
        elif self.virtual_chassis:
            return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})'
        elif self.device_type:
            return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})'
        else:
            return ''  # Device has not yet been created

    @property
    def identifier(self):
        """
        Return the device name if set; otherwise return the Device's primary key as {pk}
        """
        if self.name is not None:
            return self.name
        return '{{{}}}'.format(self.pk)

    @property
    def primary_ip(self):
        if settings.PREFER_IPV4 and self.primary_ip4:
            return self.primary_ip4
        elif self.primary_ip6:
            return self.primary_ip6
        elif self.primary_ip4:
            return self.primary_ip4
        else:
            return None

    def get_vc_master(self):
        """
        If this Device is a VirtualChassis member, return the VC master. Otherwise, return None.
        """
        return self.virtual_chassis.master if self.virtual_chassis else None

    @property
    def vc_interfaces(self):
        """
        Return a QuerySet matching all Interfaces assigned to this Device or, if this Device is a VC master, to another
        Device belonging to the same VirtualChassis.
        """
        filter = Q(device=self)
        if self.virtual_chassis and self.virtual_chassis.master == self:
            filter |= Q(device__virtual_chassis=self.virtual_chassis,
                        mgmt_only=False)
        return Interface.objects.filter(filter)

    def get_cables(self, pk_list=False):
        """
        Return a QuerySet or PK list matching all Cables connected to a component of this Device.
        """
        cable_pks = []
        for component_model in [
                ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet,
                Interface, FrontPort, RearPort
        ]:
            cable_pks += component_model.objects.filter(
                device=self, cable__isnull=False).values_list('cable',
                                                              flat=True)
        if pk_list:
            return cable_pks
        return Cable.objects.filter(pk__in=cable_pks)

    def get_children(self):
        """
        Return the set of child Devices installed in DeviceBays within this Device.
        """
        return Device.objects.filter(parent_bay__device=self.pk)

    def get_status_class(self):
        return self.STATUS_CLASS_MAP.get(self.status)
Exemple #16
0
class Dataset(StatusModel, TimeStampedModel):
    """dataset container class

    A `Dataset` represents a set of ``Datafile`s that belong together.
    This is mostly a container for `Datafile`s.
    """

    # meta
    class Meta:
        app_label = "djspikeval"
        get_latest_by = "modified"
        ordering = ("-modified", "name")

    # choices
    STATUS = AccessChoices

    # fields
    name = models.CharField(
        _("name"),
        max_length=255,
        help_text="The name will be used as an identifier for the Dataset. "
        "(character limit: 255)")
    description = models.TextField(
        blank=True,
        help_text="Use this field to give a detailed description of the "
        "Dataset. Although there is no limit to the content "
        "of this field, you may want to provide an attached file "
        "if your space or editing requirements are not met. "
        "(character limit: none)")
    parameter = models.CharField(
        max_length=255,
        default="No.",
        help_text=
        "Individual datafiles of the dataset can have a parameter attached "
        "that can be used to order and distinguish the datafiles. This may "
        "be a simulation or experimental parameter that has been varied "
        "systematically or just a numbering (default). "
        "(character limit: 255)")
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        default=2,
        help_text="The user who contributed this dataset.")

    # managers
    kind = TaggableManager(
        _("Dataset Kind"),
        help_text="A comma-separated list of tags classifying the Dataset.",
        blank=True)
    asset_set = GenericRelation("base.Asset")

    @property
    def attachment_set(self):
        return self.asset_set.filter(kind="attachment")

    @property
    def module_enabled_set(self):
        return self.module_set.filter(enabled=True)

    # methods
    def __unicode__(self):
        return unicode("Dataset: {}".format(self.name))

    @models.permalink
    def get_absolute_url(self):
        return "dataset:detail", (self.pk, ), {}

    @models.permalink
    def get_delete_url(self):
        return "dataset:delete", (self.pk, ), {}

    def is_public(self):
        return self.status == Dataset.STATUS.public

    def is_editable(self, user):
        return self.user == user or getattr(user, "is_superuser",
                                            False) is True

    def is_accessible(self, user):
        return self.is_public() or self.is_editable(user)

    def toggle(self):
        if self.status == Dataset.STATUS.public:
            self.status = Dataset.STATUS.private
        else:
            self.status = Dataset.STATUS.public
        self.save()

    def datafile_set_valid(self):
        return self.datafile_set.filter(
            ~models.Q(valid_rd_log__contains="ERROR"),
            ~models.Q(valid_gt_log__contains="ERROR"))

    def submission_count(self, user=None):
        try:
            if user.is_superuser():
                return self.submission_set.count()
            return self.submission_set.filter(
                models.Q(status__exact="public")
                | models.Q(owner_id=user.pk)).count()
        except:
            return self.submission_set.filter(status__exact="public").count()
Exemple #17
0
class DeviceType(ChangeLoggedModel, CustomFieldModel):
    """
    A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
    well as high-level functional role(s).

    Each DeviceType can have an arbitrary number of component templates assigned to it, which define console, power, and
    interface objects. For example, a Juniper EX4300-48T DeviceType would have:

      * 1 ConsolePortTemplate
      * 2 PowerPortTemplates
      * 48 InterfaceTemplates

    When a new Device of this type is created, the appropriate console, power, and interface objects (as defined by the
    DeviceType) are automatically created as well.
    """
    manufacturer = models.ForeignKey(to='dcim.Manufacturer',
                                     on_delete=models.PROTECT,
                                     related_name='device_types')
    model = models.CharField(max_length=50)
    slug = models.SlugField()
    part_number = models.CharField(max_length=50,
                                   blank=True,
                                   help_text='Discrete part number (optional)')
    u_height = models.PositiveSmallIntegerField(default=1,
                                                verbose_name='Height (U)')
    is_full_depth = models.BooleanField(
        default=True,
        verbose_name='Is full depth',
        help_text='Device consumes both front and rear rack faces')
    subdevice_role = models.CharField(
        max_length=50,
        choices=SubdeviceRoleChoices,
        blank=True,
        verbose_name='Parent/child status',
        help_text=
        'Parent devices house child devices in device bays. Leave blank '
        'if this device type is neither a parent nor a child.')
    front_image = models.ImageField(upload_to='devicetype-images', blank=True)
    rear_image = models.ImageField(upload_to='devicetype-images', blank=True)
    comments = models.TextField(blank=True)
    custom_field_values = GenericRelation(to='extras.CustomFieldValue',
                                          content_type_field='obj_type',
                                          object_id_field='obj_id')
    tags = TaggableManager(through=TaggedItem)

    objects = RestrictedQuerySet.as_manager()

    clone_fields = [
        'manufacturer',
        'u_height',
        'is_full_depth',
        'subdevice_role',
    ]

    class Meta:
        ordering = ['manufacturer', 'model']
        unique_together = [
            ['manufacturer', 'model'],
            ['manufacturer', 'slug'],
        ]

    def __str__(self):
        return self.model

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Save a copy of u_height for validation in clean()
        self._original_u_height = self.u_height

        # Save references to the original front/rear images
        self._original_front_image = self.front_image
        self._original_rear_image = self.rear_image

    def get_absolute_url(self):
        return reverse('dcim:devicetype', args=[self.pk])

    def to_yaml(self):
        data = OrderedDict((
            ('manufacturer', self.manufacturer.name),
            ('model', self.model),
            ('slug', self.slug),
            ('part_number', self.part_number),
            ('u_height', self.u_height),
            ('is_full_depth', self.is_full_depth),
            ('subdevice_role', self.subdevice_role),
            ('comments', self.comments),
        ))

        # Component templates
        if self.consoleporttemplates.exists():
            data['console-ports'] = [{
                'name': c.name,
                'type': c.type,
            } for c in self.consoleporttemplates.all()]
        if self.consoleserverporttemplates.exists():
            data['console-server-ports'] = [{
                'name': c.name,
                'type': c.type,
            } for c in self.consoleserverporttemplates.all()]
        if self.powerporttemplates.exists():
            data['power-ports'] = [{
                'name': c.name,
                'type': c.type,
                'maximum_draw': c.maximum_draw,
                'allocated_draw': c.allocated_draw,
            } for c in self.powerporttemplates.all()]
        if self.poweroutlettemplates.exists():
            data['power-outlets'] = [{
                'name':
                c.name,
                'type':
                c.type,
                'power_port':
                c.power_port.name if c.power_port else None,
                'feed_leg':
                c.feed_leg,
            } for c in self.poweroutlettemplates.all()]
        if self.interfacetemplates.exists():
            data['interfaces'] = [{
                'name': c.name,
                'type': c.type,
                'mgmt_only': c.mgmt_only,
            } for c in self.interfacetemplates.all()]
        if self.frontporttemplates.exists():
            data['front-ports'] = [{
                'name': c.name,
                'type': c.type,
                'rear_port': c.rear_port.name,
                'rear_port_position': c.rear_port_position,
            } for c in self.frontporttemplates.all()]
        if self.rearporttemplates.exists():
            data['rear-ports'] = [{
                'name': c.name,
                'type': c.type,
                'positions': c.positions,
            } for c in self.rearporttemplates.all()]
        if self.devicebaytemplates.exists():
            data['device-bays'] = [{
                'name': c.name,
            } for c in self.devicebaytemplates.all()]

        return yaml.dump(dict(data), sort_keys=False)

    def clean(self):

        # If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have
        # room to expand within their racks. This validation will impose a very high performance penalty when there are
        # many instances to check, but increasing the u_height of a DeviceType should be a very rare occurrence.
        if self.pk and self.u_height > self._original_u_height:
            for d in Device.objects.filter(device_type=self,
                                           position__isnull=False):
                face_required = None if self.is_full_depth else d.face
                u_available = d.rack.get_available_units(
                    u_height=self.u_height,
                    rack_face=face_required,
                    exclude=[d.pk])
                if d.position not in u_available:
                    raise ValidationError({
                        'u_height':
                        "Device {} in rack {} does not have sufficient space to accommodate a height of "
                        "{}U".format(d, d.rack, self.u_height)
                    })

        # If modifying the height of an existing DeviceType to 0U, check for any instances assigned to a rack position.
        elif self.pk and self._original_u_height > 0 and self.u_height == 0:
            racked_instance_count = Device.objects.filter(
                device_type=self, position__isnull=False).count()
            if racked_instance_count:
                url = f"{reverse('dcim:device_list')}?manufactuer_id={self.manufacturer_id}&device_type_id={self.pk}"
                raise ValidationError({
                    'u_height':
                    mark_safe(
                        f'Unable to set 0U height: Found <a href="{url}">{racked_instance_count} instances</a> already '
                        f'mounted within racks.')
                })

        if (self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
            ) and self.devicebaytemplates.count():
            raise ValidationError({
                'subdevice_role':
                "Must delete all device bay templates associated with this device before "
                "declassifying it as a parent device."
            })

        if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD:
            raise ValidationError(
                {'u_height': "Child device types must be 0U."})

    def save(self, *args, **kwargs):
        ret = super().save(*args, **kwargs)

        # Delete any previously uploaded image files that are no longer in use
        if self.front_image != self._original_front_image:
            self._original_front_image.delete(save=False)
        if self.rear_image != self._original_rear_image:
            self._original_rear_image.delete(save=False)

        return ret

    def delete(self, *args, **kwargs):
        super().delete(*args, **kwargs)

        # Delete any uploaded image files
        if self.front_image:
            self.front_image.delete(save=False)
        if self.rear_image:
            self.rear_image.delete(save=False)

    @property
    def display_name(self):
        return f'{self.manufacturer.name} {self.model}'

    @property
    def is_parent_device(self):
        return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT

    @property
    def is_child_device(self):
        return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
Exemple #18
0
class PublicBody(models.Model):
    name = models.CharField(_("Name"), max_length=255)
    other_names = models.TextField(_("Other names"), default="", blank=True)
    slug = models.SlugField(_("Slug"), max_length=255)
    description = models.TextField(_("Description"), blank=True)
    url = models.URLField(_("URL"), null=True, blank=True, max_length=500)
    parent = models.ForeignKey('PublicBody',
                               null=True,
                               blank=True,
                               default=None,
                               on_delete=models.SET_NULL,
                               related_name="children")
    root = models.ForeignKey('PublicBody',
                             null=True,
                             blank=True,
                             default=None,
                             on_delete=models.SET_NULL,
                             related_name="descendants")
    depth = models.SmallIntegerField(default=0)
    classification = models.CharField(_("Classification"),
                                      max_length=255,
                                      blank=True)
    classification_slug = models.SlugField(_("Classification Slug"),
                                           max_length=255,
                                           blank=True)

    email = models.EmailField(_("Email"), null=True, blank=True)
    contact = models.TextField(_("Contact"), blank=True)
    address = models.TextField(_("Address"), blank=True)
    website_dump = models.TextField(_("Website Dump"), null=True, blank=True)
    request_note = models.TextField(_("request note"), blank=True)

    file_index = models.CharField(_("file index"), max_length=1024, blank=True)
    org_chart = models.CharField(_("organisational chart"),
                                 max_length=1024,
                                 blank=True)

    _created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    verbose_name=_("Created by"),
                                    blank=True,
                                    null=True,
                                    related_name='public_body_creators',
                                    on_delete=models.SET_NULL,
                                    default=1)
    _updated_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    verbose_name=_("Updated by"),
                                    blank=True,
                                    null=True,
                                    related_name='public_body_updaters',
                                    on_delete=models.SET_NULL,
                                    default=1)
    created_at = models.DateTimeField(_("Created at"), default=timezone.now)
    updated_at = models.DateTimeField(_("Updated at"), default=timezone.now)
    confirmed = models.BooleanField(_("confirmed"), default=True)

    number_of_requests = models.IntegerField(_("Number of requests"),
                                             default=0)
    site = models.ForeignKey(Site,
                             verbose_name=_("Site"),
                             null=True,
                             on_delete=models.SET_NULL,
                             default=settings.SITE_ID)

    jurisdiction = models.ForeignKey(Jurisdiction,
                                     verbose_name=_('Jurisdiction'),
                                     blank=True,
                                     null=True,
                                     on_delete=models.SET_NULL)

    laws = models.ManyToManyField(
        FoiLaw, verbose_name=_("Freedom of Information Laws"))
    tags = TaggableManager(through=TaggedPublicBody, blank=True)

    non_filtered_objects = models.Manager()
    objects = PublicBodyManager()
    published = objects

    class Meta:
        ordering = ('name', )
        verbose_name = _("Public Body")
        verbose_name_plural = _("Public Bodies")

    serializable_fields = ('name', 'slug', 'request_note_html', 'description',
                           'url', 'email', 'contact', 'address', 'domain')

    def __str__(self):
        return u"%s (%s)" % (self.name, self.jurisdiction)

    @property
    def created_by(self):
        return self._created_by

    @property
    def updated_by(self):
        return self._updated_by

    @property
    def domain(self):
        if self.url:
            return self.url.split("/")[2]
        return None

    @property
    def request_note_html(self):
        return markdown(self.request_note)

    @property
    def tag_list(self):
        return edit_string_for_tags(self.tags.all())

    @property
    def default_law(self):
        return FoiLaw.get_default_law(self)

    def get_absolute_url(self):
        return reverse('publicbody-show', kwargs={"slug": self.slug})

    def get_absolute_domain_url(self):
        return u"%s%s" % (settings.SITE_URL, self.get_absolute_url())

    def get_label(self):
        return mark_safe(
            '%(name)s - <a href="%(url)s" class="target-new info-link">%(detail)s</a>'
            % {
                "name": escape(self.name),
                "url": self.get_absolute_url(),
                "detail": _("More Info")
            })

    def confirm(self):
        if self.confirmed:
            return None
        self.confirmed = True
        self.save()
        counter = 0
        for request in self.foirequest_set.all():
            if request.confirmed_public_body():
                counter += 1
        return counter

    def as_json(self):
        d = {}
        for field in self.serializable_fields:
            d[field] = getattr(self, field)
        d['laws'] = [self.default_law.as_dict()]
        d['jurisdiction'] = self.jurisdiction.name
        return json.dumps(d)

    @property
    def children_count(self):
        return len(PublicBody.objects.filter(parent=self))

    @classmethod
    def export_csv(cls, queryset):
        fields = (
            "id",
            "name",
            "email",
            "contact",
            "address",
            "url",
            "classification",
            "jurisdiction__slug",
            "tags",
            "other_names",
            "website_dump",
            "description",
            "request_note",
            "parent__name",
        )

        return export_csv(queryset, fields)
Exemple #19
0
class Book(models.Model):
    name = models.CharField(max_length=50, verbose_name='عنوان')
    subject = models.CharField(max_length=100, verbose_name='موضوع')
    code = models.CharField(max_length=13,
                            blank=False,
                            unique=True,
                            null=False,
                            verbose_name='شابک')
    publisher = models.ForeignKey(Publisher,
                                  on_delete=models.CASCADE,
                                  verbose_name='ناشر')
    mainCat = models.ForeignKey(Categori,
                                on_delete=models.SET_DEFAULT,
                                default=1,
                                related_name='main',
                                verbose_name='نوع محصول')
    secondCat = models.ForeignKey(Categori,
                                  on_delete=models.SET_NULL,
                                  blank=True,
                                  null=True,
                                  related_name='second',
                                  verbose_name='دسته فرعی')
    thirdCat = models.ForeignKey(Categori,
                                 on_delete=models.SET_NULL,
                                 blank=True,
                                 null=True,
                                 related_name='third',
                                 verbose_name='دسته فرعی دوم')
    description = models.CharField(max_length=200,
                                   verbose_name='توضیحات',
                                   blank=True,
                                   null=True)
    writer = models.ForeignKey(Author,
                               on_delete=models.CASCADE,
                               verbose_name='نویسنده',
                               blank=True,
                               null=True)
    translator = models.ForeignKey(Translator,
                                   on_delete=models.CASCADE,
                                   verbose_name='مترجم',
                                   blank=True,
                                   null=True)
    image = models.ImageField(storage=fs,
                              upload_to='Books',
                              verbose_name='تصویر',
                              default=defaultImagePathBook)
    #tamin konande ha
    Dakheli = models.CharField(max_length=20,
                               verbose_name='داخلی',
                               default='NULL',
                               blank=True)
    Gostaresh = models.CharField(max_length=20,
                                 verbose_name='گسترش',
                                 default='NULL',
                                 blank=True)
    Ghoghnus = models.CharField(max_length=20,
                                verbose_name='ققنوس',
                                default='NULL',
                                blank=True)
    Asar = models.CharField(max_length=20,
                            verbose_name='اثار',
                            default='NULL',
                            blank=True)
    GitaMehr = models.CharField(max_length=20,
                                verbose_name='گیتا مهر',
                                default='NULL',
                                blank=True)
    Elias = models.CharField(max_length=20,
                             verbose_name='الیاس',
                             default='NULL',
                             blank=True)
    PezeshkiPasargad = models.CharField(max_length=20,
                                        verbose_name='پاسارگاد',
                                        default='NULL',
                                        blank=True)
    PaiamNur = models.CharField(max_length=20,
                                verbose_name='پیام نور',
                                default='NULL',
                                blank=True)
    Jungle = models.CharField(max_length=20,
                              verbose_name='جنگل',
                              default='NULL',
                              blank=True)
    Bidgol = models.CharField(max_length=20,
                              verbose_name='بیدگل',
                              default='NULL',
                              blank=True)
    GotenBerg = models.CharField(max_length=20,
                                 verbose_name='گتنبرگ',
                                 default='NULL',
                                 blank=True)
    Kharazmi = models.CharField(max_length=20,
                                verbose_name='خوارزمی',
                                default='NULL',
                                blank=True)
    SimaieDanesh = models.CharField(max_length=20,
                                    verbose_name='سیمای دانش',
                                    default='NULL',
                                    blank=True)
    #---------------------------
    inventory = models.BooleanField(default=False, verbose_name='موجودی')
    price = models.DecimalField(decimal_places=0,
                                max_digits=6,
                                verbose_name='قیمت')
    printNumber = models.IntegerField(blank=True,
                                      null=True,
                                      verbose_name='نوبت چاپ')
    publishYear = models.CharField(max_length=4,
                                   blank=True,
                                   null=True,
                                   verbose_name='سال چاپ')
    created_time = models.DateField(editable=False)
    updated_time = models.DateField(editable=False)
    language = models.SmallIntegerField(default=0,
                                        choices=((a, b)
                                                 for a, b in langs.items()),
                                        verbose_name='زبان کتاب')
    ghat = models.SmallIntegerField(default=0,
                                    choices=((a, b) for a, b in ghats.items()),
                                    verbose_name='قطع')
    jeld = models.SmallIntegerField(default=0,
                                    choices=((a, b) for a, b in jelds.items()),
                                    verbose_name='نوع جلد')
    pages = models.DecimalField(max_digits=4,
                                decimal_places=0,
                                verbose_name='تعداد صفحه',
                                blank=True,
                                null=True)
    weight = models.DecimalField(max_digits=5,
                                 decimal_places=0,
                                 verbose_name='وزن',
                                 null=True,
                                 blank=True)
    likes = models.IntegerField(default=0, verbose_name='پسندیدم ها')
    dilikes = models.IntegerField(default=0, verbose_name='نپسندیدم ها')
    views = models.DecimalField(default=0,
                                max_digits=4,
                                decimal_places=0,
                                verbose_name='بازدید')
    discount = models.DecimalField(default=0,
                                   max_digits=3,
                                   decimal_places=0,
                                   verbose_name='تخفیف')
    sales = models.DecimalField(default=0,
                                max_digits=4,
                                decimal_places=0,
                                verbose_name='تعداد فروش',
                                editable=False)
    available = models.BooleanField(default=True, verbose_name='قابل خرید')
    visiblity = models.BooleanField(default=True, verbose_name='نمایش')
    stock = models.DecimalField(default=4,
                                max_digits=2,
                                decimal_places=0,
                                verbose_name='قابل خرید')
    tags = TaggableManager()
    objects = BookManager()

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.id:
            self.created_time = timezone.now()
        self.updated_time = timezone.now()
        return super(Book, self).save(*args, **kwargs)

    def getTaminKonande(self):
        all = {
            'Dakheli': self.Dakheli,
            'Gostaresh': self.Gostaresh,
            'Ghoghuns': self.Ghoghnus,
            'Asar': self.Asar,
            'Bidgol': self.Bidgol,
            'GotenBerg': self.GotenBerg,
            'Elias': self.Elias,
            'Kharazmi': self.Kharazmi,
            'Jungle': self.Jungle,
            'PaiamNur': self.PaiamNur,
            'PezeshkiPasargad': self.PezeshkiPasargad,
            'SimaieDanesh': self.SimaieDanesh
        }
        taminkonande = ''
        max = -1
        if self.inventory == True:
            for key in all.keys():
                if all[key] != 'NULL':
                    newMax = taminKondandeOlaviat[key]
                    if max < newMax:
                        max = newMax
                        taminkonande = key
            if max != -1:
                return self.__dict__[taminkonande]
        else:
            return 'NULL'

    def getRealPrice(self):
        return int(self.price)

    def calculatePriceWithDiscount(self):  #book price with discount
        if self.discount > 0:
            dis = (int(self.discount) / 100) * int(self.price)
            return {'stat': True, 'price': int(int(self.price) - dis)}
        return {'stat': False, 'price': self.price}

    def getBookDiscount(self):  #discount of a book
        if self.discount > 0:
            return (int(self.discount) / 100) * int(self.price)
        return 0
Exemple #20
0
class Media(models.Model):
    media_type = None
    help_text = None
    icon = None

    # details
    name = models.CharField(max_length=120, null=True, blank=True)
    description = models.TextField(null=True, blank=True)
    slug = models.SlugField(
        unique=True,
        blank=True,
        help_text=_('Leave this field blank for autopopulating a unique tag '
                    'based on the objects name.'))

    # categorization
    tags = TaggableManager(blank=True)
    content_type = models.ForeignKey(ContentType, editable=False)

    created = models.DateTimeField(auto_now_add=True)

    # managers
    objects = MediaManager()

    class Meta:
        app_label = 'mediastore'
        verbose_name = _('media')
        verbose_name_plural = _('media')
        ordering = ('created', )

    def __repr__(self):
        name = 'Media'
        if self.__class__ is not Media:
            name = 'Media:%s' % self.__class__.__name__
        return '<%s: pk=%d "%s">' % (name, self.pk, self.name or '')

    def __unicode__(self):
        return self.name or ''

    def get_media_type(self):
        if self.media_type:
            return self.media_type
        return self.content_type.model_class().media_type

    def generate_slug(self):
        from mediastore.utils import unique_slug
        return unique_slug(self.name or self.pk, Media)

    def save(self, *args, **kwargs):
        if self.content_type_id is None:
            media_content_type = ContentType.objects.get_for_model(Media)
            content_type = ContentType.objects.get_for_model(self)
            if content_type != media_content_type:
                self.content_type = content_type
        if not self.content_type:
            raise AssertionError(
                '''This media instance has no associated media type. Access Media only by subclasses.'''
            )
        if not self.slug:
            self.slug = self.generate_slug()
        super(Media, self).save(*args, **kwargs)

    @property
    def object(self):
        '''
        Returns the related media item.
        '''
        if not hasattr(self, '_related_media_instance'):
            self._related_media_instance = None
            if is_media_instance(self):
                self._related_media_instance = self
            else:
                model = self.content_type.model_class()
                o2o_rel = model._meta.get_field('media_ptr')
                attname = o2o_rel.related_query_name()
                self._related_media_instance = getattr(self, attname)
        return self._related_media_instance
Exemple #21
0
class Project(ModelBase):
    """Placeholder model for projects."""
    object_type = object_types['group']

    name = models.CharField(max_length=100)

    # Select kind of project (study group, course, or other)
    STUDY_GROUP = 'study group'
    COURSE = 'course'
    CHALLENGE = 'challenge'
    CATEGORY_CHOICES = (
        (STUDY_GROUP, _('Study Group -- group of people working ' \
                        'collaboratively to acquire and share knowledge.')),
        (COURSE, _('Course -- led by one or more organizers with skills on ' \
                   'a field who direct and help participants during their ' \
                   'learning.')),
        (CHALLENGE, _('Challenge -- series of tasks peers can engage in ' \
                      'to develop skills.'))
    )
    category = models.CharField(max_length=30,
                                choices=CATEGORY_CHOICES,
                                default=STUDY_GROUP,
                                null=True,
                                blank=False)

    tags = TaggableManager(through=GeneralTaggedItem, blank=True)

    other = models.CharField(max_length=30, blank=True, null=True)
    other_description = models.CharField(max_length=150, blank=True, null=True)

    short_description = models.CharField(max_length=150)
    long_description = RichTextField(validators=[MaxLengthValidator(700)])

    start_date = models.DateField(null=True, blank=True)
    end_date = models.DateField(null=True, blank=True)

    school = models.ForeignKey('schools.School',
                               related_name='projects',
                               null=True,
                               blank=True)

    detailed_description = models.ForeignKey('content.Page',
                                             related_name='desc_project',
                                             null=True,
                                             blank=True)

    image = models.ImageField(upload_to=determine_image_upload_path,
                              null=True,
                              storage=storage.ImageStorage(),
                              blank=True)

    slug = models.SlugField(unique=True, max_length=110)
    featured = models.BooleanField(default=False)
    created_on = models.DateTimeField(auto_now_add=True,
                                      default=datetime.datetime.now)

    under_development = models.BooleanField(default=True)
    not_listed = models.BooleanField(default=False)
    archived = models.BooleanField(default=False)

    clone_of = models.ForeignKey('projects.Project',
                                 blank=True,
                                 null=True,
                                 related_name='derivated_projects')

    imported_from = models.CharField(max_length=150, blank=True, null=True)

    next_projects = models.ManyToManyField('projects.Project',
                                           symmetrical=False,
                                           related_name='previous_projects',
                                           blank=True,
                                           null=True)

    objects = ProjectManager()

    class Meta:
        verbose_name = _('group')

    def __unicode__(self):
        return _('%(name)s %(kind)s') % dict(name=self.name,
                                             kind=self.kind.lower())

    @models.permalink
    def get_absolute_url(self):
        return ('projects_show', (), {
            'slug': self.slug,
        })

    def friendly_verb(self, verb):
        if verbs['post'] == verb:
            return _('created')

    @property
    def kind(self):
        return self.other.lower() if self.other else self.category

    def followers(self, include_deleted=False):
        relationships = Relationship.objects.all()
        if not include_deleted:
            relationships = relationships.filter(source__deleted=False)
        return relationships.filter(target_project=self, deleted=False)

    def previous_followers(self, include_deleted=False):
        """Return a list of users who were followers if this project."""
        relationships = Relationship.objects.all()
        if not include_deleted:
            relationships = relationships.filter(source__deleted=False)
        return relationships.filter(target_project=self, deleted=True)

    def non_participant_followers(self, include_deleted=False):
        return self.followers(include_deleted).exclude(
            source__id__in=self.participants(include_deleted).values(
                'user_id'))

    def participants(self, include_deleted=False):
        """Return a list of users participating in this project."""
        participations = Participation.objects.all()
        if not include_deleted:
            participations = participations.filter(user__deleted=False)
        return participations.filter(project=self, left_on__isnull=True)

    def non_organizer_participants(self, include_deleted=False):
        return self.participants(include_deleted).filter(organizing=False)

    def adopters(self, include_deleted=False):
        return self.participants(include_deleted).filter(
            Q(adopter=True) | Q(organizing=True))

    def non_adopter_participants(self, include_deleted=False):
        return self.non_organizer_participants(include_deleted).filter(
            adopter=False)

    def organizers(self, include_deleted=False):
        return self.participants(include_deleted).filter(organizing=True)

    def is_organizing(self, user):
        if user.is_authenticated():
            profile = user.get_profile()
            is_organizer = self.organizers().filter(user=profile).exists()
            is_superuser = user.is_superuser
            return is_organizer or is_superuser
        else:
            return False

    def is_following(self, user):
        if user.is_authenticated():
            profile = user.get_profile()
            is_following = self.followers().filter(source=profile).exists()
            return is_following
        else:
            return False

    def is_participating(self, user):
        if user.is_authenticated():
            profile = user.get_profile()
            is_organizer_or_participant = self.participants().filter(
                user=profile).exists()
            is_superuser = user.is_superuser
            return is_organizer_or_participant or is_superuser
        else:
            return False

    def get_metrics_permissions(self, user):
        """Provides metrics related permissions for metrics overview
        and csv download."""
        if user.is_authenticated():
            if user.is_superuser:
                return True, True
            allowed_schools = settings.STATISTICS_ENABLED_SCHOOLS
            if not self.school or self.school.slug not in allowed_schools:
                return False, False
            csv_downloaders = settings.STATISTICS_CSV_DOWNLOADERS
            profile = user.get_profile()
            csv_permission = profile.username in csv_downloaders
            is_school_organizer = self.school.organizers.filter(
                id=user.id).exists()
            if is_school_organizer or self.is_organizing(user):
                return True, csv_permission
        return False, False

    def activities(self):
        return Activity.objects.filter(
            deleted=False, scope_object=self).order_by('-created_on')

    def create(self):
        self.save()
        self.send_creation_notification()

    def save(self):
        """Make sure each project has a unique slug."""
        count = 1
        if not self.slug:
            slug = slugify(self.name)
            self.slug = slug
            while True:
                existing = Project.objects.filter(slug=self.slug)
                if len(existing) == 0:
                    break
                self.slug = "%s-%s" % (slug, count + 1)
                count += 1
        super(Project, self).save()

    def get_image_url(self):
        missing = settings.MEDIA_URL + 'images/project-missing.png'
        image_path = self.image.url if self.image else missing
        return image_path

    def send_creation_notification(self):
        """Send notification when a new project is created."""
        context = {
            'project': self,
            'domain': Site.objects.get_current().domain,
        }
        subjects, bodies = localize_email(
            'projects/emails/project_created_subject.txt',
            'projects/emails/project_created.txt', context)
        for organizer in self.organizers():
            SendUserEmail.apply_async((organizer.user, subjects, bodies))
        admin_subject = render_to_string(
            "projects/emails/admin_project_created_subject.txt",
            context).strip()
        admin_body = render_to_string(
            "projects/emails/admin_project_created.txt", context).strip()
        for admin_email in settings.ADMIN_PROJECT_CREATE_EMAIL:
            send_mail(admin_subject,
                      admin_body,
                      admin_email, [admin_email],
                      fail_silently=True)

    def accepted_school(self):
        # Used previously when schools had to decline groups.
        return self.school

    def check_tasks_completion(self, user):
        total_count = self.pages.filter(listed=True, deleted=False).count()
        completed_count = PerUserTaskCompletion.objects.filter(
            page__project=self,
            page__deleted=False,
            unchecked_on__isnull=True,
            user=user).count()
        if total_count == completed_count:
            badges = self.get_project_badges(only_self_completion=True)
            for badge in badges:
                badge.award_to(user)

    def completed_tasks_users(self):
        total_count = self.pages.filter(listed=True, deleted=False).count()
        completed_stats = PerUserTaskCompletion.objects.filter(
            page__project=self, page__deleted=False,
            unchecked_on__isnull=True).values('user__username').annotate(
                completed_count=Count('page')).filter(
                    completed_count=total_count)
        usernames = completed_stats.values('user__username')
        return Relationship.objects.filter(source__username__in=usernames,
                                           target_project=self,
                                           source__deleted=False)

    def get_project_badges(self,
                           only_self_completion=False,
                           only_peer_skill=False,
                           only_peer_community=False):
        from badges.models import Badge
        assessment_types = []
        badge_types = []
        if not only_self_completion and not only_peer_community:
            assessment_types.append(Badge.PEER)
            badge_types.append(Badge.SKILL)
        if not only_peer_skill and not only_peer_community:
            assessment_types.append(Badge.SELF)
            badge_types.append(Badge.COMPLETION)
        if not only_peer_skill and not only_self_completion:
            assessment_types.append(Badge.PEER)
            badge_types.append(Badge.COMMUNITY)
        if assessment_types and badge_types:
            return self.badges.filter(assessment_type__in=assessment_types,
                                      badge_type__in=badge_types)
        else:
            return Badge.objects.none()

    def get_upon_completion_badges(self, user):
        from badges.models import Badge, Award
        if user.is_authenticated():
            profile = user.get_profile()
            awarded_badges = Award.objects.filter(
                user=profile).values('badge_id')
            self_completion_badges = self.get_project_badges(
                only_self_completion=True)
            upon_completion_badges = []
            for badge in self_completion_badges:
                missing_prerequisites = badge.prerequisites.exclude(
                    id__in=awarded_badges).exclude(
                        id__in=self_completion_badges.values('id'))
                if not missing_prerequisites.exists():
                    upon_completion_badges.append(badge.id)
            return Badge.objects.filter(id__in=upon_completion_badges)
        else:
            return Badge.objects.none()

    def get_awarded_badges(self, user, only_peer_skill=False):
        from badges.models import Badge, Award
        if user.is_authenticated():
            profile = user.get_profile()
            awarded_badges = Award.objects.filter(
                user=profile).values('badge_id')
            project_badges = self.get_project_badges(only_peer_skill)
            return project_badges.filter(id__in=awarded_badges)
        else:
            return Badge.objects.none()

    def get_badges_in_progress(self, user):
        from badges.models import Badge, Award, Submission
        if user.is_authenticated():
            profile = user.get_profile()
            awarded_badges = Award.objects.filter(
                user=profile).values('badge_id')
            attempted_badges = Submission.objects.filter(
                author=profile).values('badge_id')
            project_badges = self.get_project_badges(only_peer_skill=True)
            return project_badges.filter(id__in=attempted_badges).exclude(
                id__in=awarded_badges)
        else:
            return Badge.objects.none()

    def get_non_attempted_badges(self, user):
        from badges.models import Badge, Award, Submission
        if user.is_authenticated():
            profile = user.get_profile()
            awarded_badges = Award.objects.filter(
                user=profile).values('badge_id')
            attempted_badges = Submission.objects.filter(
                author=profile).values('badge_id')
            project_badges = self.get_project_badges(only_peer_skill=True)
            # Excluding both awarded and attempted badges
            # In case honorary award do not rely on submissions.
            return project_badges.exclude(id__in=attempted_badges).exclude(
                id__in=awarded_badges)
        else:
            return Badge.objects.none()

    def get_need_reviews_badges(self, user):
        from badges.models import Badge, Award, Submission
        if user.is_authenticated():
            profile = user.get_profile()
            project_badges = self.get_project_badges(only_peer_skill=True)
            peers_submissions = Submission.objects.filter(
                badge__id__in=project_badges.values('id')).exclude(
                    author=profile)
            peers_attempted_badges = project_badges.filter(
                id__in=peers_submissions.values('badge_id'))
            need_reviews_badges = []
            for badge in peers_attempted_badges:
                peers_awards = Award.objects.filter(badge=badge).exclude(
                    user=profile)
                pending_submissions = peers_submissions.filter(
                    badge=badge).exclude(
                        author__id__in=peers_awards.values('user_id'))
                if pending_submissions.exists():
                    need_reviews_badges.append(badge.id)
            return project_badges.filter(id__in=need_reviews_badges)
        else:
            return Badge.objects.none()

    def get_non_started_next_projects(self, user):
        """To be displayed in the Join Next Challenges section."""
        if user.is_authenticated():
            profile = user.get_profile()
            joined = Participation.objects.filter(
                user=profile).values('project_id')
            return self.next_projects.exclude(id__in=joined)
        else:
            return Project.objects.none()

    @staticmethod
    def filter_activities(activities):
        from statuses.models import Status
        content_types = [
            ContentType.objects.get_for_model(Page),
            ContentType.objects.get_for_model(PageComment),
            ContentType.objects.get_for_model(Status),
            ContentType.objects.get_for_model(Project),
        ]
        return activities.filter(target_content_type__in=content_types)

    @staticmethod
    def filter_learning_activities(activities):
        pages_ct = ContentType.objects.get_for_model(Page)
        comments_ct = ContentType.objects.get_for_model(PageComment)
        return activities.filter(
            target_content_type__in=[pages_ct, comments_ct])
Exemple #22
0
class Instance(models.Model):
    json = JSONField(default={}, null=False)
    xml = models.TextField()
    user = models.ForeignKey(User, related_name='instances', null=True)
    xform = models.ForeignKey(XForm, null=True, related_name='instances')
    survey_type = models.ForeignKey(SurveyType)

    # shows when we first received this instance
    date_created = models.DateTimeField(auto_now_add=True)

    # this will end up representing "date last parsed"
    date_modified = models.DateTimeField(auto_now=True)

    # this will end up representing "date instance was deleted"
    deleted_at = models.DateTimeField(null=True, default=None)

    # ODK keeps track of three statuses for an instance:
    # incomplete, submitted, complete
    # we add a fourth status: submitted_via_web
    status = models.CharField(max_length=20,
                              default=u'submitted_via_web')
    uuid = models.CharField(max_length=249, default=u'')
    version = models.CharField(max_length=XFORM_TITLE_LENGTH, null=True)

    # store an geographic objects associated with this instance
    geom = models.GeometryCollectionField(null=True)
    objects = models.GeoManager()

    tags = TaggableManager()

    class Meta:
        app_label = 'logger'

    @classmethod
    def set_deleted_at(cls, instance_id, deleted_at=timezone.now()):
        try:
            instance = cls.objects.get(id=instance_id)
        except cls.DoesNotExist:
            pass
        else:
            instance.set_deleted(deleted_at)

    def _check_active(self, force):
        """Check that form is active and raise exception if not.

        :param force: Ignore restrictions on saving.
        """
        if not force and self.xform and not self.xform.downloadable:
            raise FormInactiveError()

    def _set_geom(self):
        xform = self.xform
        data_dictionary = xform.data_dictionary()
        geo_xpaths = data_dictionary.geopoint_xpaths()
        doc = self.get_dict()
        points = []

        if len(geo_xpaths):
            for xpath in geo_xpaths:
                geometry = [float(s) for s in doc.get(xpath, u'').split()]

                if len(geometry):
                    lat, lng = geometry[0:2]
                    points.append(Point(lng, lat))

            if not xform.instances_with_geopoints and len(points):
                xform.instances_with_geopoints = True
                xform.save()

            self.geom = GeometryCollection(points)

    def _set_json(self):
        doc = self.get_dict()

        if not self.date_created:
            now = submission_time()
            self.date_created = now

        point = self.point
        if point:
            doc[GEOLOCATION] = [point.y, point.x]

        doc[SUBMISSION_TIME] = self.date_created.strftime(MONGO_STRFTIME)
        doc[XFORM_ID_STRING] = self._parser.get_xform_id_string()
        doc[SUBMITTED_BY] = self.user.username\
            if self.user is not None else None
        self.json = doc

    def _set_parser(self):
        if not hasattr(self, "_parser"):
            self._parser = XFormInstanceParser(
                self.xml, self.xform.data_dictionary())

    def _set_survey_type(self):
        self.survey_type, created = \
            SurveyType.objects.get_or_create(slug=self.get_root_node_name())

    def _set_uuid(self):
        if self.xml and not self.uuid:
            uuid = get_uuid_from_xml(self.xml)
            if uuid is not None:
                self.uuid = uuid
        set_uuid(self)

    def get(self, abbreviated_xpath):
        self._set_parser()
        return self._parser.get(abbreviated_xpath)

    def get_dict(self, force_new=False, flat=True):
        """Return a python object representation of this instance's XML."""
        self._set_parser()

        return self._parser.get_flat_dict_with_attributes() if flat else\
            self._parser.to_dict()

    def get_full_dict(self):
        # TODO should we store all of these in the JSON no matter what?
        d = self.json
        data = {
            UUID: self.uuid,
            ID: self.id,
            BAMBOO_DATASET_ID: self.xform.bamboo_dataset,
            self.USERFORM_ID: u'%s_%s' % (
                self.user.username,
                self.xform.id_string),
            ATTACHMENTS: [a.media_file.name for a in
                          self.attachments.all()],
            self.STATUS: self.status,
            TAGS: list(self.tags.names()),
            NOTES: self.get_notes(),
            VERSION: self.version
        }

        if isinstance(self.instance.deleted_at, datetime):
            data[DELETEDAT] = self.deleted_at.strftime(MONGO_STRFTIME)

        d.update(data)

        return d

    def get_notes(self):
        return [note['note'] for note in self.notes.values('note')]

    def get_root_node(self):
        self._set_parser()
        return self._parser.get_root_node()

    def get_root_node_name(self):
        self._set_parser()
        return self._parser.get_root_node_name()

    @property
    def point(self):
        gc = self.geom

        if gc and len(gc):
            return gc[0]

    def save(self, *args, **kwargs):
        force = kwargs.get('force')

        if force:
            del kwargs['force']

        self._check_active(force)

        self._set_geom()
        self._set_json()
        self._set_survey_type()
        self._set_uuid()
        self.version = self.xform.version
        super(Instance, self).save(*args, **kwargs)

    def set_deleted(self, deleted_at=timezone.now()):
        self.deleted_at = deleted_at
        self.save()
        # force submission count re-calculation
        self.xform.submission_count(force_update=True)
        self.parsed_instance.save()
class Poll(models.Model):
    poll_collection = models.ForeignKey(PollCollection,
                                        on_delete=models.CASCADE,
                                        related_name='polls')

    RADIO = 'RA'
    CHECKBOX = 'CE'
    TEXT = 'TX'
    PRIO = 'PR'
    YESNONONE = 'Y3'

    TYPE_CHOICES = (
        (RADIO, 'Einfachauswahl'),
        (CHECKBOX, 'Mehrfachauswahl'),
        (TEXT, 'Freitext'),
        (PRIO, 'Priorisierung'),
        (YESNONONE, 'Ja-Nein-Enthaltung'),
    )

    poll_type = models.CharField(max_length=2,
                                 choices=TYPE_CHOICES,
                                 default=RADIO)

    question = models.CharField(max_length=255, unique=False)
    description = models.TextField(default="")

    is_published = models.BooleanField(default=True)

    show_percent = models.BooleanField(default=True)

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

    tags = TaggableManager(blank=True)

    position = models.SmallIntegerField(default=1)

    max_votes = models.SmallIntegerField(default=-1, blank=True, null=True)

    class Meta:
        verbose_name = "Frage"
        verbose_name_plural = "Fragen"
        ordering = ["position", "id"]

    def __unicode__(self):
        return self.question

    def __str__(self):
        return self.question

    @property
    def is_text(self):
        return self.poll_type == Poll.TEXT

    @property
    def is_prio(self):
        return self.poll_type == Poll.PRIO

    @property
    def is_yes_no_none(self):
        return self.poll_type == Poll.YESNONONE

    @property
    def items(self):
        return Item.objects.filter(poll=self).order_by('position', 'id')

    @property
    def results(self):
        ret = []
        for item in self.items:
            count = self.vote_count
            if count == 0:
                ret += [(item, 0, 0)]
            else:
                if self.is_prio:
                    votes = Vote.objects.filter(item=item)
                    if len(votes):
                        avg = sum(int(v.text)
                                  for v in votes) / float(votes.count())
                    else:
                        avg = 0.0
                    ret += [(item, "%.1f" % avg, int(avg / 5.0 * 100))]
                elif self.is_yes_no_none:
                    votes = Vote.objects.filter(item=item)
                    stimmen = dict(ja=0, nein=0, enthaltung=0)
                    for v in votes:
                        stimmen[v.text] += 1

                    label = "Ja: {ja}, Nein: {nein}, Enthaltung: {enthaltung}".format(
                        **stimmen)
                    ret += [(item, label, 0)]
                else:
                    percent = int(item.vote_count / float(count) * 100)
                    ret += [(item, percent, percent)]
        return ret

    @property
    def votes(self):
        return Vote.objects \
                   .filter(poll=self)

    def has_voted(self, user):
        return Vote.objects \
                   .filter(poll=self, user=user)\
                   .exists()

    def get_unvoted(self, user):
        polls = Poll.objects.all()\
                    .exclude(vote__user=me)\
                    .filter(poll_collection__is_active=True,
                            poll_collection__is_published=True)
        return [p for p in polls if p.can_vote(user)]

    @property
    def vote_count(self):
        return self.votes.aggregate(
            votes=Count('user', distinct=True))['votes']
Exemple #24
0
class ResourceBase(models.Model, PermissionLevelMixin, ThumbnailMixin):
    """
    Base Resource Object loosely based on ISO 19115:2003
    """

    VALID_DATE_TYPES = [(x.lower(), _(x))
                        for x in ['Creation', 'Publication', 'Revision']]

    # internal fields
    uuid = models.CharField(max_length=36)
    owner = models.ForeignKey(User, blank=True, null=True)

    contacts = models.ManyToManyField(Profile, through='ContactRole')

    # section 1
    title = models.CharField(
        _('title'),
        max_length=255,
        help_text=_('name by which the cited resource is known'))
    date = models.DateTimeField(
        _('date'),
        default=datetime.now,
        help_text=_('reference date for the cited resource'
                    ))  # passing the method itself, not the result

    date_type = models.CharField(
        _('date type'),
        max_length=255,
        choices=VALID_DATE_TYPES,
        default='publication',
        help_text=_('identification of when a given event occurred'))

    edition = models.CharField(_('edition'),
                               max_length=255,
                               blank=True,
                               null=True,
                               help_text=_('version of the cited resource'))
    abstract = models.TextField(
        _('abstract'),
        blank=True,
        help_text=_(
            'brief narrative summary of the content of the resource(s)'))
    purpose = models.TextField(
        _('purpose'),
        null=True,
        blank=True,
        help_text=_(
            'summary of the intentions with which the resource(s) was developed'
        ))

    maintenance_frequency = models.CharField(
        _('maintenance frequency'),
        max_length=255,
        choices=UPDATE_FREQUENCIES,
        blank=True,
        null=True,
        help_text=
        _('frequency with which modifications and deletions are made to the data after it is first produced'
          ))

    # section 2
    # see poc property definition below

    # section 3
    keywords = TaggableManager(
        _('keywords'),
        blank=True,
        help_text=
        _('commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject (space or comma-separated'
          ))
    regions = models.ManyToManyField(
        Region,
        verbose_name=_('keywords region'),
        help_text=_('keyword identifies a location'),
        blank=True)
    restriction_code_type = models.ForeignKey(
        RestrictionCodeType,
        verbose_name=_('restrictions'),
        help_text=_(
            'limitation(s) placed upon the access or use of the data.'),
        null=True,
        blank=True,
        limit_choices_to=Q(is_choice=True))
    constraints_other = models.TextField(
        _('restrictions other'),
        blank=True,
        null=True,
        help_text=
        _('other restrictions and legal prerequisites for accessing and using the resource or metadata'
          ))

    # Section 4
    language = models.CharField(
        _('language'),
        max_length=3,
        choices=ALL_LANGUAGES,
        default='eng',
        help_text=_('language used within the dataset'))
    category = models.ForeignKey(
        TopicCategory,
        help_text=
        _('high-level geographic data thematic classification to assist in the grouping and search of available geographic data sets.'
          ),
        null=True,
        blank=True,
        limit_choices_to=Q(is_choice=True),
        default=get_default_category)
    spatial_representation_type = models.ForeignKey(
        SpatialRepresentationType,
        help_text=_(
            'method used to represent geographic information in the dataset.'),
        null=True,
        blank=True,
        limit_choices_to=Q(is_choice=True))

    # Section 5
    temporal_extent_start = models.DateField(
        _('temporal extent start'),
        blank=True,
        null=True,
        help_text=_(
            'time period covered by the content of the dataset (start)'))
    temporal_extent_end = models.DateField(
        _('temporal extent end'),
        blank=True,
        null=True,
        help_text=_('time period covered by the content of the dataset (end)'))

    supplemental_information = models.TextField(
        _('supplemental information'),
        default=DEFAULT_SUPPLEMENTAL_INFORMATION,
        help_text=_('any other descriptive information about the dataset'))

    # Section 6
    distribution_url = models.TextField(
        _('distribution URL'),
        blank=True,
        null=True,
        help_text=
        _('information about on-line sources from which the dataset, specification, or community profile name and extended metadata elements can be obtained'
          ))
    distribution_description = models.TextField(
        _('distribution description'),
        blank=True,
        null=True,
        help_text=_(
            'detailed text description of what the online resource is/does'))

    # Section 8
    data_quality_statement = models.TextField(
        _('data quality statement'),
        blank=True,
        null=True,
        help_text=
        _('general explanation of the data producer\'s knowledge about the lineage of a dataset'
          ))

    # Section 9
    # see metadata_author property definition below

    # Save bbox values in the database.
    # This is useful for spatial searches and for generating thumbnail images and metadata records.
    bbox_x0 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    bbox_x1 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    bbox_y0 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    bbox_y1 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    srid = models.CharField(max_length=255, default='EPSG:4326')

    # CSW specific fields
    csw_typename = models.CharField(_('CSW typename'),
                                    max_length=32,
                                    default='gmd:MD_Metadata',
                                    null=False)
    csw_schema = models.CharField(_('CSW schema'),
                                  max_length=64,
                                  default='http://www.isotc211.org/2005/gmd',
                                  null=False)
    csw_mdsource = models.CharField(_('CSW source'),
                                    max_length=256,
                                    default='local',
                                    null=False)
    csw_insert_date = models.DateTimeField(_('CSW insert date'),
                                           auto_now_add=True,
                                           null=True)
    csw_type = models.CharField(_('CSW type'),
                                max_length=32,
                                default='dataset',
                                null=False,
                                choices=HIERARCHY_LEVELS)
    csw_anytext = models.TextField(_('CSW anytext'), null=True, blank=True)
    csw_wkt_geometry = models.TextField(
        _('CSW WKT geometry'),
        null=False,
        default='POLYGON((-180 -90,-180 90,180 90,180 -90,-180 -90))')

    # metadata XML specific fields
    metadata_uploaded = models.BooleanField(default=False)
    metadata_xml = models.TextField(
        null=True,
        default=
        '<gmd:MD_Metadata xmlns:gmd="http://www.isotc211.org/2005/gmd"/>',
        blank=True)

    thumbnail = models.ForeignKey(Thumbnail, null=True, blank=True)

    def __unicode__(self):
        return self.title

    @property
    def bbox(self):
        return [
            self.bbox_x0, self.bbox_y0, self.bbox_x1, self.bbox_y1, self.srid
        ]

    @property
    def bbox_string(self):
        return ",".join([
            str(self.bbox_x0),
            str(self.bbox_y0),
            str(self.bbox_x1),
            str(self.bbox_y1)
        ])

    @property
    def geographic_bounding_box(self):
        return bbox_to_wkt(self.bbox_x0,
                           self.bbox_x1,
                           self.bbox_y0,
                           self.bbox_y1,
                           srid=self.srid)

    def get_extent(self):
        """Generate minx/miny/maxx/maxy of map extent"""

        return self.bbox

    @property
    def poc_role(self):
        role = Role.objects.get(value='pointOfContact')
        return role

    @property
    def metadata_author_role(self):
        role = Role.objects.get(value='author')
        return role

    def keyword_list(self):
        return [kw.name for kw in self.keywords.all()]

    @property
    def keyword_csv(self):
        keywords_qs = self.keywords.all()
        if keywords_qs:
            return ','.join([kw.name for kw in keywords_qs])
        else:
            return ''

    def set_latlon_bounds(self, box):
        """
        Set the four bounds in lat lon projection
        """
        self.bbox_x0 = box[0]
        self.bbox_x1 = box[1]
        self.bbox_y0 = box[2]
        self.bbox_y1 = box[3]

    def download_links(self):
        """assemble download links for pycsw"""
        links = []
        for url in self.link_set.all():
            if url.link_type == 'metadata':  # avoid recursion
                continue
            if url.link_type == 'html':
                links.append((self.title, 'Web address (URL)',
                              'WWW:LINK-1.0-http--link', url.url))
            else:
                description = '%s (%s Format)' % (self.title, url.name)
                links.append((self.title, description,
                              'WWW:DOWNLOAD-1.0-http--download', url.url))
        return links

    def _set_poc(self, poc):
        # reset any poc asignation to this resource
        ContactRole.objects.filter(role=self.poc_role, resource=self).delete()
        #create the new assignation
        ContactRole.objects.create(role=self.poc_role,
                                   resource=self,
                                   contact=poc)

    def _get_poc(self):
        try:
            the_poc = ContactRole.objects.get(role=self.poc_role,
                                              resource=self).contact
        except ContactRole.DoesNotExist:
            the_poc = None
        return the_poc

    poc = property(_get_poc, _set_poc)

    def _set_metadata_author(self, metadata_author):
        # reset any metadata_author asignation to this resource
        ContactRole.objects.filter(role=self.metadata_author_role,
                                   resource=self).delete()
        #create the new assignation
        ContactRole.objects.create(role=self.metadata_author_role,
                                   resource=self,
                                   contact=metadata_author)

    def _get_metadata_author(self):
        try:
            the_ma = ContactRole.objects.get(role=self.metadata_author_role,
                                             resource=self).contact
        except ContactRole.DoesNotExist:
            the_ma = None
        return the_ma

    metadata_author = property(_get_metadata_author, _set_metadata_author)
Exemple #25
0
class Version(models.Model):
    project = models.ForeignKey(Project,
                                verbose_name=_('Project'),
                                related_name='versions')
    type = models.CharField(
        _('Type'),
        max_length=20,
        choices=VERSION_TYPES,
        default='unknown',
    )
    # used by the vcs backend
    identifier = models.CharField(_('Identifier'), max_length=255)

    verbose_name = models.CharField(_('Verbose Name'), max_length=255)
    slug = models.CharField(_('Slug'), max_length=255)

    supported = models.BooleanField(_('Supported'), default=True)
    active = models.BooleanField(_('Active'), default=False)
    built = models.BooleanField(_('Built'), default=False)
    uploaded = models.BooleanField(_('Uploaded'), default=False)
    privacy_level = models.CharField(
        _('Privacy Level'),
        max_length=20,
        choices=constants.PRIVACY_CHOICES,
        default='public',
        help_text=_("Level of privacy for this Version."))
    tags = TaggableManager(blank=True)
    objects = VersionManager()

    class Meta:
        unique_together = [('project', 'slug')]
        ordering = ['-verbose_name']
        permissions = (
            # Translators: Permission around whether a user can view the
            #              version
            ('view_version', _('View Version')), )

    def __unicode__(self):
        return ugettext(u"Version %(version)s of %(project)s (%(pk)s)" % {
            'version': self.verbose_name,
            'project': self.project,
            'pk': self.pk
        })

    def get_absolute_url(self):
        if not self.built and not self.uploaded:
            return ''
        return self.project.get_docs_url(version_slug=self.slug)

    def save(self, *args, **kwargs):
        """
        Add permissions to the Version for all owners on save.
        """
        obj = super(Version, self).save(*args, **kwargs)
        for owner in self.project.users.all():
            assign('view_version', owner, self)
        self.project.sync_supported_versions()
        return obj

    @property
    def remote_slug(self):
        if self.slug == 'latest':
            if self.project.default_branch:
                return self.project.default_branch
            else:
                return self.project.vcs_repo().fallback_branch
        else:
            return self.slug

    def get_subdomain_url(self):
        use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False)
        if use_subdomain:
            return "/%s/%s/" % (
                self.project.language,
                self.slug,
            )
        else:
            return reverse('docs_detail',
                           kwargs={
                               'project_slug': self.project.slug,
                               'lang_slug': self.project.language,
                               'version_slug': self.slug,
                               'filename': ''
                           })

    def get_subproject_url(self):
        return "/projects/%s/%s/%s/" % (
            self.project.slug,
            self.project.language,
            self.slug,
        )

    def get_downloads(self, pretty=False):
        project = self.project
        data = {}
        if pretty:
            if project.has_pdf(self.slug):
                data['PDF'] = project.get_pdf_url(self.slug)
            if project.has_htmlzip(self.slug):
                data['HTML'] = project.get_htmlzip_url(self.slug)
            if project.has_epub(self.slug):
                data['Epub'] = project.get_epub_url(self.slug)
        else:
            if project.has_pdf(self.slug):
                data['pdf_url'] = project.get_pdf_url(self.slug)
            if project.has_htmlzip(self.slug):
                data['htmlzip_url'] = project.get_htmlzip_url(self.slug)
            if project.has_epub(self.slug):
                data['epub_url'] = project.get_epub_url(self.slug)
            #if project.has_manpage(self.slug):
            #data['manpage_url'] = project.get_manpage_url(self.slug)
            if project.has_dash(self.slug):
                data['dash_url'] = project.get_dash_url(self.slug)
                data['dash_feed_url'] = project.get_dash_feed_url(self.slug)
        return data

    def get_conf_py_path(self):
        # Hack this for now.
        return "/docs/"
        conf_py_path = self.project.conf_file(self.slug)
        conf_py_path = conf_py_path.replace(
            self.project.checkout_path(self.slug), '')
        return conf_py_path.replace('conf.py', '')

    def get_build_path(self):
        '''Return version build path if path exists, otherwise `None`'''
        path = self.project.checkout_path(version=self.slug)
        if os.path.exists(path):
            return path
        return None

    def get_github_url(self,
                       docroot,
                       filename,
                       source_suffix='.rst',
                       action='view'):
        GITHUB_REGEXS = [
            re.compile('github.com/(.+)/(.+)(?:\.git){1}'),
            re.compile('github.com/(.+)/(.+)'),
            re.compile('github.com:(.+)/(.+).git'),
        ]
        GITHUB_URL = 'https://github.com/{user}/{repo}/{action}/{version}{docroot}{path}{source_suffix}'

        repo_url = self.project.repo
        if 'github' not in repo_url:
            return ''
        if not docroot:
            return ''

        if action == 'view':
            action_string = 'blob'
        elif action == 'edit':
            action_string = 'edit'

        for regex in GITHUB_REGEXS:
            match = regex.search(repo_url)
            if match:
                user, repo = match.groups()
                break
        else:
            return ''
        repo = repo.rstrip('/')

        return GITHUB_URL.format(
            user=user,
            repo=repo,
            version=self.remote_slug,
            docroot=docroot,
            path=filename,
            source_suffix=source_suffix,
            action=action_string,
        )

    def get_bitbucket_url(self, docroot, filename, source_suffix='.rst'):
        BB_REGEXS = [
            re.compile('bitbucket.org/(.+)/(.+).git'),
            re.compile('bitbucket.org/(.+)/(.+)/'),
            re.compile('bitbucket.org/(.+)/(.+)'),
        ]
        BB_URL = 'https://bitbucket.org/{user}/{repo}/src/{version}{docroot}{path}{source_suffix}'

        repo_url = self.project.repo
        if 'bitbucket' not in repo_url:
            return ''
        if not docroot:
            return ''

        for regex in BB_REGEXS:
            match = regex.search(repo_url)
            if match:
                user, repo = match.groups()
                break
        else:
            return ''
        repo = repo.rstrip('/')

        return BB_URL.format(
            user=user,
            repo=repo,
            version=self.remote_slug,
            docroot=docroot,
            path=filename,
            source_suffix=source_suffix,
        )
Exemple #26
0
class Conversation(HasFavoriteMixin, TimeStampedModel):
    """
    A topic of conversation.
    """

    title = models.CharField(
        _("Title"),
        max_length=255,
        help_text=_(
            "Short description used to create URL slugs (e.g. School system)."
        ),
    )
    text = models.TextField(_("Question"),
                            help_text=_("What do you want to ask?"))
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="conversations",
        help_text=
        _("Only the author and administrative staff can edit this conversation."
          ),
    )
    moderators = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        blank=True,
        related_name="moderated_conversations",
        help_text=_("Moderators can accept and reject comments."),
    )
    slug = AutoSlugField(unique=False, populate_from="title")
    is_promoted = models.BooleanField(
        _("Promote conversation?"),
        default=False,
        help_text=_(
            "Promoted conversations appears in the main /conversations/ "
            "endpoint."),
    )
    is_hidden = models.BooleanField(
        _("Hide conversation?"),
        default=False,
        help_text=_(
            "Hidden conversations does not appears in boards or in the main /conversations/ "
            "endpoint."),
    )

    objects = ConversationQuerySet.as_manager()
    tags = TaggableManager(through="ConversationTag", blank=True)
    votes = property(
        lambda self: Vote.objects.filter(comment__conversation=self))

    @property
    def users(self):
        return get_user_model().objects.filter(
            votes__comment__conversation=self).distinct()

    # Comment managers
    def _filter_comments(*args):
        *_, which = args
        status = getattr(Comment.STATUS, which)
        return property(lambda self: self.comments.filter(status=status))

    approved_comments = _filter_comments("approved")
    rejected_comments = _filter_comments("rejected")
    pending_comments = _filter_comments("pending")
    del _filter_comments

    class Meta:
        ordering = ["created"]
        verbose_name = _("Conversation")
        verbose_name_plural = _("Conversations")
        permissions = (
            ("can_publish_promoted", _("Can publish promoted conversations")),
            ("is_moderator", _("Can moderate comments in any conversation")),
        )

    #
    # Statistics and annotated values
    #
    author_name = lazy(this.author.name)
    first_tag = lazy(this.tags.values_list("name", flat=True).first())
    tag_names = lazy(this.tags.values_list("name", flat=True))

    # Statistics
    n_comments = deprecate_lazy(
        this.n_approved_comments,
        "Conversation.n_comments was deprecated in favor of .n_approved_comments."
    )
    n_approved_comments = lazy(this.approved_comments.count())
    n_pending_comments = lazy(this.pending_comments.count())
    n_rejected_comments = lazy(this.rejected_comments.count())
    n_total_comments = lazy(this.comments.count().count())

    n_favorites = lazy(this.favorites.count())
    n_tags = lazy(this.tags.count())
    n_votes = lazy(this.votes.count())
    n_final_votes = lazy(this.votes.exclude(choice=Choice.SKIP).count())
    n_participants = lazy(this.users.count())

    # Statistics for the request user
    user_comments = property(this.comments.filter(author=this.for_user))
    user_votes = property(this.votes.filter(author=this.for_user))
    n_user_total_comments = lazy(this.user_comments.count())
    n_user_comments = lazy(
        this.user_comments.filter(status=Comment.STATUS.approved).count())
    n_user_rejected_comments = lazy(
        this.user_comments.filter(status=Comment.STATUS.rejected).count())
    n_user_pending_comments = lazy(
        this.user_comments.filter(status=Comment.STATUS.pending).count())
    n_user_votes = lazy(this.user_votes.count())
    n_user_final_votes = lazy(
        this.user_votes.exclude(choice=Choice.SKIP).count())
    is_user_favorite = lazy(this.is_favorite(this.for_user))

    # Statistical methods
    vote_count = vote_count
    statistics = statistics
    statistics_for_user = statistics_for_user

    @lazy
    def for_user(self):
        return self.request.user

    @lazy
    def request(self):
        msg = "Set the request object by calling the .set_request(request) method first"
        raise RuntimeError(msg)

    # TODO: move as patches from other apps
    @lazy
    def n_clusters(self):
        try:
            return self.clusterization.n_clusters
        except AttributeError:
            return 0

    @lazy
    def n_stereotypes(self):
        try:
            return self.clusterization.n_clusters
        except AttributeError:
            return 0

    n_endorsements = 0  # FIXME: endorsements

    def __str__(self):
        return self.title

    def set_request(self, request_or_user):
        """
        Saves optional user and request attributes in model. Those attributes are
        used to compute and cache many other attributes and statistics in the
        conversation model instance.
        """
        request = None
        user = request_or_user
        if not isinstance(request_or_user, get_user_model()):
            user = request_or_user.user
            request = request_or_user

        if self.__dict__.get("for_user", user) != user or self.__dict__.get(
                "request", request) != request:
            raise ValueError("user/request already set in conversation!")

        self.for_user = user
        self.request = request

    def save(self, *args, **kwargs):
        if self.id is None:
            pass
        super().save(*args, **kwargs)

    def clean(self):
        can_edit = "ej.can_edit_conversation"
        if self.is_promoted and self.author_id is not None and not self.author.has_perm(
                can_edit, self):
            raise ValidationError(
                _("User does not have permission to create a promoted "
                  "conversation."))

    def get_absolute_url(self, board=None):
        kwargs = {"conversation": self, "slug": self.slug}
        if board is None:
            board = getattr(self, "board", None)
        if board:
            kwargs["board"] = board
            return SafeUrl("boards:conversation-detail", **kwargs)
        else:
            return SafeUrl("conversation:detail", **kwargs)

    def url(self, which="conversation:detail", board=None, **kwargs):
        """
        Return a url pertaining to the current conversation.
        """
        if board is None:
            board = getattr(self, "board", None)

        kwargs["conversation"] = self
        kwargs["slug"] = self.slug

        if board:
            kwargs["board"] = board
            which = "boards:" + which.replace(":", "-")
            return SafeUrl(which, **kwargs)

        return SafeUrl(which, **kwargs)

    def votes_for_user(self, user):
        """
        Get all votes in conversation for the given user.
        """
        if user.id is None:
            return Vote.objects.none()
        return self.votes.filter(author=user)

    def create_comment(self,
                       author,
                       content,
                       commit=True,
                       *,
                       status=None,
                       check_limits=True,
                       **kwargs):
        """
        Create a new comment object for the given user.

        If commit=True (default), comment is persisted on the database.

        By default, this method check if the user can post according to the
        limits imposed by the conversation. It also normalizes duplicate
        comments and reuse duplicates from the database.
        """

        # Convert status, if necessary
        if status is None and (author.id == self.author.id or author.has_perm(
                "ej.can_edit_conversation", self)):
            kwargs["status"] = Comment.STATUS.approved

        else:
            kwargs["status"] = normalize_status(status)

        # Check limits
        if check_limits and not author.has_perm("ej.can_comment", self):
            log.info("failed attempt to create comment by %s" % author)
            raise PermissionError("user cannot comment on conversation.")

        # Check if comment is created with rejected status
        if status == Comment.STATUS.rejected:
            msg = _("automatically rejected")
            kwargs.setdefault("rejection_reason", msg)

        kwargs.update(author=author, content=content.strip())
        comment = make_clean(Comment, commit, conversation=self, **kwargs)
        if comment.status == comment.STATUS.approved and author != self.author:
            comment_moderated.send(
                Comment,
                comment=comment,
                moderator=comment.moderator,
                is_approved=True,
                author=comment.author,
            )
        log.info("new comment: %s" % comment)
        return comment

    def next_comment(self, user, default=NOT_GIVEN):
        """
        Returns a random comment that user didn't vote yet.

        If default value is not given, raises a Comment.DoesNotExit exception
        if no comments are available for user.
        """
        comment = rules.compute("ej.next_comment", self, user)
        if comment:
            return comment
        return None

    def next_comment_with_id(self, user, comment_id=None):
        """
        Returns a comment with id if user didn't vote yet, otherwhise return
        a random comment.
        """
        if comment_id:
            try:
                return self.approved_comments.exclude(votes__author=user).get(
                    id=comment_id)
            except Exception as e:
                pass
        return self.next_comment(user)
Exemple #27
0
class ResourceBase(PolymorphicModel, PermissionLevelMixin):
    """
    Base Resource Object loosely based on ISO 19115:2003
    """

    VALID_DATE_TYPES = [(x.lower(), _(x))
                        for x in ['Creation', 'Publication', 'Revision']]

    date_help_text = _('reference date for the cited resource')
    date_type_help_text = _('identification of when a given event occurred')
    edition_help_text = _('version of the cited resource')
    abstract_help_text = _(
        'brief narrative summary of the content of the resource(s)')
    purpose_help_text = _(
        'summary of the intentions with which the resource(s) was developed')
    maintenance_frequency_help_text = _(
        'frequency with which modifications and deletions are made to the data after '
        'it is first produced')
    keywords_help_text = _(
        'commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject '
        '(space or comma-separated')
    regions_help_text = _('keyword identifies a location')
    restriction_code_type_help_text = _(
        'limitation(s) placed upon the access or use of the data.')
    constraints_other_help_text = _(
        'other restrictions and legal prerequisites for accessing and using the resource or'
        ' metadata')
    license_help_text = _('license of the dataset')
    language_help_text = _('language used within the dataset')
    category_help_text = _(
        'high-level geographic data thematic classification to assist in the grouping and search of '
        'available geographic data sets.')
    spatial_representation_type_help_text = _(
        'method used to represent geographic information in the dataset.')
    temporal_extent_start_help_text = _(
        'time period covered by the content of the dataset (start)')
    temporal_extent_end_help_text = _(
        'time period covered by the content of the dataset (end)')
    data_quality_statement_help_text = _(
        'general explanation of the data producer\'s knowledge about the lineage of a'
        ' dataset')
    # internal fields
    uuid = models.CharField(max_length=36)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              blank=True,
                              null=True,
                              related_name='owned_resource',
                              verbose_name=_("Owner"))
    contacts = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                      through='ContactRole')
    title = models.CharField(
        _('title'),
        max_length=255,
        help_text=_('name by which the cited resource is known'))
    date = models.DateTimeField(_('date'),
                                default=datetime.datetime.now,
                                help_text=date_help_text)
    date_type = models.CharField(_('date type'),
                                 max_length=255,
                                 choices=VALID_DATE_TYPES,
                                 default='publication',
                                 help_text=date_type_help_text)
    edition = models.CharField(_('edition'),
                               max_length=255,
                               blank=True,
                               null=True,
                               help_text=edition_help_text)
    abstract = models.TextField(_('abstract'),
                                blank=True,
                                help_text=abstract_help_text)
    purpose = models.TextField(_('purpose'),
                               null=True,
                               blank=True,
                               help_text=purpose_help_text)
    maintenance_frequency = models.CharField(
        _('maintenance frequency'),
        max_length=255,
        choices=UPDATE_FREQUENCIES,
        blank=True,
        null=True,
        help_text=maintenance_frequency_help_text)

    keywords = TaggableManager(_('keywords'),
                               blank=True,
                               help_text=keywords_help_text)
    regions = models.ManyToManyField(Region,
                                     verbose_name=_('keywords region'),
                                     blank=True,
                                     null=True,
                                     help_text=regions_help_text)

    restriction_code_type = models.ForeignKey(
        RestrictionCodeType,
        verbose_name=_('restrictions'),
        help_text=restriction_code_type_help_text,
        null=True,
        blank=True,
        limit_choices_to=Q(is_choice=True))

    constraints_other = models.TextField(_('restrictions other'),
                                         blank=True,
                                         null=True,
                                         help_text=constraints_other_help_text)

    license = models.ForeignKey(License,
                                null=True,
                                blank=True,
                                verbose_name=_("License"),
                                help_text=license_help_text)
    language = models.CharField(_('language'),
                                max_length=3,
                                choices=ALL_LANGUAGES,
                                default='eng',
                                help_text=language_help_text)

    category = models.ForeignKey(TopicCategory,
                                 null=True,
                                 blank=True,
                                 limit_choices_to=Q(is_choice=True),
                                 help_text=category_help_text)

    spatial_representation_type = models.ForeignKey(
        SpatialRepresentationType,
        null=True,
        blank=True,
        limit_choices_to=Q(is_choice=True),
        verbose_name=_("spatial representation type"),
        help_text=spatial_representation_type_help_text)

    # Section 5
    temporal_extent_start = models.DateTimeField(
        _('temporal extent start'),
        blank=True,
        null=True,
        help_text=temporal_extent_start_help_text)
    temporal_extent_end = models.DateTimeField(
        _('temporal extent end'),
        blank=True,
        null=True,
        help_text=temporal_extent_end_help_text)

    supplemental_information = models.TextField(
        _('supplemental information'),
        default=DEFAULT_SUPPLEMENTAL_INFORMATION,
        help_text=_('any other descriptive information about the dataset'))

    # Section 8
    data_quality_statement = models.TextField(
        _('data quality statement'),
        blank=True,
        null=True,
        help_text=data_quality_statement_help_text)

    # Section 9
    # see metadata_author property definition below

    # Save bbox values in the database.
    # This is useful for spatial searches and for generating thumbnail images and metadata records.
    bbox_x0 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    bbox_x1 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    bbox_y0 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    bbox_y1 = models.DecimalField(max_digits=19,
                                  decimal_places=10,
                                  blank=True,
                                  null=True)
    srid = models.CharField(max_length=255, default='EPSG:4326')

    # CSW specific fields
    csw_typename = models.CharField(_('CSW typename'),
                                    max_length=32,
                                    default='gmd:MD_Metadata',
                                    null=False)

    csw_schema = models.CharField(_('CSW schema'),
                                  max_length=64,
                                  default='http://www.isotc211.org/2005/gmd',
                                  null=False)

    csw_mdsource = models.CharField(_('CSW source'),
                                    max_length=256,
                                    default='local',
                                    null=False)
    csw_insert_date = models.DateTimeField(_('CSW insert date'),
                                           auto_now_add=True,
                                           null=True)
    csw_type = models.CharField(_('CSW type'),
                                max_length=32,
                                default='dataset',
                                null=False,
                                choices=HIERARCHY_LEVELS)
    csw_anytext = models.TextField(_('CSW anytext'), null=True, blank=True)
    csw_wkt_geometry = models.TextField(
        _('CSW WKT geometry'),
        null=False,
        default='POLYGON((-180 -90,-180 90,180 90,180 -90,-180 -90))')

    # metadata XML specific fields
    metadata_uploaded = models.BooleanField(default=False)
    metadata_uploaded_preserve = models.BooleanField(default=False)
    metadata_xml = models.TextField(
        null=True,
        default=
        '<gmd:MD_Metadata xmlns:gmd="http://www.isotc211.org/2005/gmd"/>',
        blank=True)

    popular_count = models.IntegerField(default=0)
    share_count = models.IntegerField(default=0)

    featured = models.BooleanField(
        _("Featured"),
        default=False,
        help_text=_('Should this resource be advertised in home page?'))
    is_published = models.BooleanField(
        _("Is Published"),
        default=True,
        help_text=_('Should this resource be published and searchable?'))

    # fields necessary for the apis
    thumbnail_url = models.TextField(null=True, blank=True)
    detail_url = models.CharField(max_length=255, null=True, blank=True)
    rating = models.IntegerField(default=0, null=True, blank=True)

    def __unicode__(self):
        return self.title

    @property
    def bbox(self):
        return [
            self.bbox_x0, self.bbox_y0, self.bbox_x1, self.bbox_y1, self.srid
        ]

    @property
    def bbox_string(self):
        return ",".join([
            str(self.bbox_x0),
            str(self.bbox_y0),
            str(self.bbox_x1),
            str(self.bbox_y1)
        ])

    @property
    def geographic_bounding_box(self):
        return bbox_to_wkt(self.bbox_x0,
                           self.bbox_x1,
                           self.bbox_y0,
                           self.bbox_y1,
                           srid=self.srid)

    @property
    def license_light(self):
        a = []
        if (not (self.license.name is None)) and (len(self.license.name) > 0):
            a.append(self.license.name)
        if (not (self.license.url is None)) and (len(self.license.url) > 0):
            a.append("(" + self.license.url + ")")
        return " ".join(a)

    @property
    def license_verbose(self):
        a = []
        if (not (self.license.name_long is None)) and (len(
                self.license.name_long) > 0):
            a.append(self.license.name_long + ":")
        if (not (self.license.description is None)) and (len(
                self.license.description) > 0):
            a.append(self.license.description)
        if (not (self.license.url is None)) and (len(self.license.url) > 0):
            a.append("(" + self.license.url + ")")
        return " ".join(a)

    def keyword_list(self):
        return [kw.name for kw in self.keywords.all()]

    def keyword_slug_list(self):
        return [kw.slug for kw in self.keywords.all()]

    def region_name_list(self):
        return [region.name for region in self.regions.all()]

    def spatial_representation_type_string(self):
        if hasattr(self.spatial_representation_type, 'identifier'):
            return self.spatial_representation_type.identifier
        else:
            if hasattr(self, 'storeType'):
                if self.storeType == 'coverageStore':
                    return 'grid'
                return 'vector'
            else:
                return None

    @property
    def keyword_csv(self):
        keywords_qs = self.get_real_instance().keywords.all()
        if keywords_qs:
            return ','.join([kw.name for kw in keywords_qs])
        else:
            return ''

    def set_latlon_bounds(self, box):
        """
        Set the four bounds in lat lon projection
        """
        self.bbox_x0 = box[0]
        self.bbox_x1 = box[1]
        self.bbox_y0 = box[2]
        self.bbox_y1 = box[3]

    def set_bounds_from_center_and_zoom(self, center_x, center_y, zoom):
        """
        Calculate zoom level and center coordinates in mercator.
        """
        self.center_x = center_x
        self.center_y = center_y
        self.zoom = zoom

        deg_len_equator = 40075160 / 360

        # covert center in lat lon
        def get_lon_lat():
            wgs84 = Proj(init='epsg:4326')
            mercator = Proj(init='epsg:3857')
            lon, lat = transform(mercator, wgs84, center_x, center_y)
            return lon, lat

        # calculate the degree length at this latitude
        def deg_len():
            lon, lat = get_lon_lat()
            return math.cos(lat) * deg_len_equator

        lon, lat = get_lon_lat()

        # taken from http://wiki.openstreetmap.org/wiki/Zoom_levels
        # it might be not precise but enough for the purpose
        distance_per_pixel = 40075160 * math.cos(lat) / 2**(zoom + 8)

        # calculate the distance from the center of the map in degrees
        # we use the calculated degree length on the x axis and the
        # normal degree length on the y axis assumin that it does not change

        # Assuming a map of 1000 px of width and 700 px of height
        distance_x_degrees = distance_per_pixel * 500 / deg_len()
        distance_y_degrees = distance_per_pixel * 350 / deg_len_equator

        self.bbox_x0 = lon - distance_x_degrees
        self.bbox_x1 = lon + distance_x_degrees
        self.bbox_y0 = lat - distance_y_degrees
        self.bbox_y1 = lat + distance_y_degrees

    def set_bounds_from_bbox(self, bbox):
        """
        Calculate zoom level and center coordinates in mercator.
        """
        self.set_latlon_bounds(bbox)

        minx, miny, maxx, maxy = [float(c) for c in bbox]
        x = (minx + maxx) / 2
        y = (miny + maxy) / 2
        (center_x, center_y) = forward_mercator((x, y))

        xdiff = maxx - minx
        ydiff = maxy - miny

        zoom = 0

        if xdiff > 0 and ydiff > 0:
            width_zoom = math.log(360 / xdiff, 2)
            height_zoom = math.log(360 / ydiff, 2)
            zoom = math.ceil(min(width_zoom, height_zoom))

        self.zoom = zoom
        self.center_x = center_x
        self.center_y = center_y

    def download_links(self):
        """assemble download links for pycsw"""
        links = []
        for url in self.link_set.all():
            if url.link_type == 'metadata':  # avoid recursion
                continue
            if url.link_type == 'html':
                links.append((self.title, 'Web address (URL)',
                              'WWW:LINK-1.0-http--link', url.url))
            elif url.link_type in ('OGC:WMS', 'OGC:WFS', 'OGC:WCS'):
                links.append((self.title, url.name, url.link_type, url.url))
            else:
                description = '%s (%s Format)' % (self.title, url.name)
                links.append((self.title, description,
                              'WWW:DOWNLOAD-1.0-http--download', url.url))
        return links

    def get_tiles_url(self):
        """Return URL for Z/Y/X mapping clients or None if it does not exist.
        """
        try:
            tiles_link = self.link_set.get(name='Tiles')
        except Link.DoesNotExist:
            return None
        else:
            return tiles_link.url

    def get_legend(self):
        """Return Link for legend or None if it does not exist.
        """
        try:
            legends_link = self.link_set.get(name='Legend')
        except Link.DoesNotExist:
            return None
        else:
            return legends_link

    def get_legend_url(self):
        """Return URL for legend or None if it does not exist.

           The legend can be either an image (for Geoserver's WMS)
           or a JSON object for ArcGIS.
        """
        legend = self.get_legend()

        if legend is None:
            return None

        return legend.url

    def get_ows_url(self):
        """Return URL for OGC WMS server None if it does not exist.
        """
        try:
            ows_link = self.link_set.get(name='OGC:WMS')
        except Link.DoesNotExist:
            return None
        else:
            return ows_link.url

    def get_thumbnail_url(self):
        """Return a thumbnail url.

           It could be a local one if it exists, a remote one (WMS GetImage) for example
           or a 'Missing Thumbnail' one.
        """
        local_thumbnails = self.link_set.filter(name='Thumbnail')
        if local_thumbnails.count() > 0:
            return local_thumbnails[0].url

        remote_thumbnails = self.link_set.filter(name='Remote Thumbnail')
        if remote_thumbnails.count() > 0:
            return remote_thumbnails[0].url

        return staticfiles.static(settings.MISSING_THUMBNAIL)

    def has_thumbnail(self):
        """Determine if the thumbnail object exists and an image exists"""
        return self.link_set.filter(name='Thumbnail').exists()

    def save_thumbnail(self, filename, image):
        thumb_folder = 'thumbs'
        upload_path = os.path.join(settings.MEDIA_ROOT, thumb_folder)
        if not os.path.exists(upload_path):
            os.makedirs(upload_path)

        with open(os.path.join(upload_path, filename), 'wb') as f:
            thumbnail = File(f)
            thumbnail.write(image)

        url_path = os.path.join(settings.MEDIA_URL, thumb_folder,
                                filename).replace('\\', '/')
        url = urljoin(settings.SITEURL, url_path)

        Link.objects.get_or_create(resource=self,
                                   url=url,
                                   defaults=dict(
                                       name='Thumbnail',
                                       extension='png',
                                       mime='image/png',
                                       link_type='image',
                                   ))

        ResourceBase.objects.filter(id=self.id).update(thumbnail_url=url)

    def set_missing_info(self):
        """Set default permissions and point of contacts.

           It is mandatory to call it from descendant classes
           but hard to enforce technically via signals or save overriding.
        """
        from guardian.models import UserObjectPermission
        logger.debug('Checking for permissions.')
        #  True if every key in the get_all_level_info dict is empty.
        no_custom_permissions = UserObjectPermission.objects.filter(
            content_type=ContentType.objects.get_for_model(
                self.get_self_resource()),
            object_pk=str(self.pk)).exists()

        if not no_custom_permissions:
            logger.debug(
                'There are no permissions for this object, setting default perms.'
            )
            self.set_default_permissions()

        if self.owner:
            user = self.owner
        else:
            user = ResourceBase.objects.admin_contact().user

        if self.poc is None:
            self.poc = user
        if self.metadata_author is None:
            self.metadata_author = user

    def maintenance_frequency_title(self):
        return [
            v for i, v in enumerate(UPDATE_FREQUENCIES)
            if v[0] == self.maintenance_frequency
        ][0][1].title()

    def language_title(self):
        return [
            v for i, v in enumerate(ALL_LANGUAGES) if v[0] == self.language
        ][0][1].title()

    def _set_poc(self, poc):
        # reset any poc assignation to this resource
        ContactRole.objects.filter(role='pointOfContact',
                                   resource=self).delete()
        # create the new assignation
        ContactRole.objects.create(role='pointOfContact',
                                   resource=self,
                                   contact=poc)

    def _get_poc(self):
        try:
            the_poc = ContactRole.objects.get(role='pointOfContact',
                                              resource=self).contact
        except ContactRole.DoesNotExist:
            the_poc = None
        return the_poc

    poc = property(_get_poc, _set_poc)

    def _set_metadata_author(self, metadata_author):
        # reset any metadata_author assignation to this resource
        ContactRole.objects.filter(role='author', resource=self).delete()
        # create the new assignation
        ContactRole.objects.create(role='author',
                                   resource=self,
                                   contact=metadata_author)

    def _get_metadata_author(self):
        try:
            the_ma = ContactRole.objects.get(role='author',
                                             resource=self).contact
        except ContactRole.DoesNotExist:
            the_ma = None
        return the_ma

    metadata_author = property(_get_metadata_author, _set_metadata_author)

    objects = ResourceBaseManager()

    class Meta:
        # custom permissions,
        # add, change and delete are standard in django-guardian
        permissions = (
            ('view_resourcebase', 'Can view resource'),
            ('change_resourcebase_permissions',
             'Can change resource permissions'),
            ('download_resourcebase', 'Can download resource'),
            ('publish_resourcebase', 'Can publish resource'),
            ('change_resourcebase_metadata', 'Can change resource metadata'),
        )
class Question(AbstractComment):
    title = models.CharField(max_length=255)
    tags = TaggableManager(through=TaggedQuestion)

    def __str__(self):
        return self.title
Exemple #29
0
class Profile(AbstractUser):

    """Fully featured Geonode user"""

    organization = models.CharField(
        _('Organization Name'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('name of the responsible organization'))
    profile = models.TextField(
        _('Profile'),
        null=True,
        blank=True,
        help_text=_('introduce yourself'))
    position = models.CharField(
        _('Position Name'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('role or position of the responsible person'))
    voice = models.CharField(_('Voice'), max_length=255, blank=True, null=True, help_text=_(
        'telephone number by which individuals can speak to the responsible organization or individual'))
    fax = models.CharField(_('Facsimile'), max_length=255, blank=True, null=True, help_text=_(
        'telephone number of a facsimile machine for the responsible organization or individual'))
    delivery = models.CharField(
        _('Delivery Point'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('physical and email address at which the organization or individual may be contacted'))
    city = models.CharField(
        _('City'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('city of the location'))
    area = models.CharField(
        _('Administrative Area'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('state, province of the location'))
    zipcode = models.CharField(
        _('Postal Code'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('ZIP or other postal code'))
    country = models.CharField(
        _('Country'),
        choices=COUNTRIES,
        max_length=3,
        blank=True,
        null=True,
        help_text=_('country of the physical address'))
    keywords = TaggableManager(_('keywords'), blank=True, help_text=_(
        'commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject \
            (space or comma-separated'))
    language = models.CharField(
        _("language"),
        max_length=10,
        choices=LANGUAGES,
        default=settings.LANGUAGE_CODE
    )
    timezone = models.CharField(
        _('Timezone'),
        max_length=100,
        default="",
        choices=TIMEZONES,
        blank=True,
    )

    def __init__(self, *args, **kwargs):
        super(Profile, self).__init__(*args, **kwargs)
        self._previous_active_state = self.is_active

    def get_absolute_url(self):
        return reverse('profile_detail', args=[self.username, ])

    def __unicode__(self):
        return u"%s" % (self.username)

    def class_name(value):
        return value.__class__.__name__

    objects = ProfileUserManager()
    USERNAME_FIELD = 'username'

    def group_list_public(self):
        return GroupProfile.objects.exclude(
            access="private").filter(groupmember__user=self)

    def group_list_all(self):
        return GroupProfile.objects.filter(groupmember__user=self).distinct()

    def is_member_of_group(self, group_slug):
        """
        Returns if the Profile belongs to a group of a given slug.
        """
        return self.groups.filter(name=group_slug).exists()

    def keyword_list(self):
        """
        Returns a list of the Profile's keywords.
        """
        return [kw.name for kw in self.keywords.all()]

    @property
    def name_long(self):
        if self.first_name and self.last_name:
            return '%s %s (%s)' % (self.first_name,
                                   self.last_name, self.username)
        elif (not self.first_name) and self.last_name:
            return '%s (%s)' % (self.last_name, self.username)
        elif self.first_name and (not self.last_name):
            return '%s (%s)' % (self.first_name, self.username)
        else:
            return self.username

    @property
    def location(self):
        return format_address(self.delivery, self.zipcode,
                              self.city, self.area, self.country)

    def save(self, *args, **kwargs):
        super(Profile, self).save(*args, **kwargs)
        self._notify_account_activated()
        self._previous_active_state = self.is_active

    def _notify_account_activated(self):
        """Notify user that its account has been activated by a staff member"""
        became_active = self.is_active and not self._previous_active_state
        if became_active and self.last_login is None:
            try:
                # send_notification(users=(self,), label="account_active")

                from invitations.adapters import get_invitations_adapter
                current_site = Site.objects.get_current()
                ctx = {
                    'username': self.username,
                    'current_site': current_site,
                    'site_name': current_site.name,
                    'email': self.email,
                    'inviter': self,
                }

                email_template = 'pinax/notifications/account_active/account_active'

                get_invitations_adapter().send_mail(
                    email_template,
                    self.email,
                    ctx)
            except BaseException:
                import traceback
                traceback.print_exc()
Exemple #30
0
class Site(models.Model):
    """
    A news website included in the archive.
    """
    name = models.CharField(
        help_text='The formal name of the site that will display for users',
        max_length=150)
    sortable_name = models.CharField(
        help_text='The version of the name used for sorting', max_length=150)
    slug = models.SlugField(unique=True)
    url = models.URLField()
    display_url = models.URLField(blank=True)
    description = models.TextField(blank=True)
    hometown = models.CharField(max_length=500, blank=True)
    timezone = models.CharField(
        max_length=500,
        blank=True,
        choices=[(i, i) for i in common_timezones],
    )
    STATUS_CHOICES = (
        ('active', 'Active'),
        ('inactive', 'Inactive'),
    )
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default='active',
        db_index=True,
    )
    on_the_homepage = models.BooleanField(default=True)

    # Local screenshots
    has_html_screenshots = models.BooleanField(default=False)
    y_offset = models.IntegerField(default=0, blank=True)

    # Third party mementos
    has_internetarchive_mementos = models.BooleanField(
        verbose_name='has Internet Archive mementos', default=False)
    has_archiveis_mementos = models.BooleanField(
        verbose_name='has archive.is mementos', default=False)
    has_webcitation_mementos = models.BooleanField(
        verbose_name="has webcitation.org mementos", default=False)

    # Managers
    objects = managers.SiteManager()
    tags = TaggableManager(blank=True)

    class Meta:
        ordering = (
            'sortable_name',
            'name',
        )

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        return ("archive-site-detail", [self.slug])

    @models.permalink
    def get_timemap_index_url(self):
        return ("timemap-url-link-feed", [], dict(url=self.url))