def __init__(self, *args, **kw):
     # The line below is needed to bypass this
     # https://github.com/django/django/commit/572885729e028eae2f2b823ef87543b7c66bdb10
     # thanks to MarkusH @ freenode for help
     self.attname = self.related = '(this is a hack)'
     # this is needed when filtering(this__contains=x, this__not_contains=y)
     self.null = False
     GenericForeignKey.__init__(self, *args, **kw)
Example #2
0
class Widget(OrderedModel):
    CHARTS = 'charts'

    # Other widgets types to be added later
    WIDGETS_TYPES = ((CHARTS, 'Charts'), )

    # Will hold either XForm or DataView Model
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    widget_type = models.CharField(max_length=25,
                                   choices=WIDGETS_TYPES,
                                   default=CHARTS)
    view_type = models.CharField(max_length=50)
    column = models.CharField(max_length=255)
    group_by = models.CharField(null=True,
                                default=None,
                                max_length=255,
                                blank=True)

    title = models.CharField(null=True,
                             default=None,
                             max_length=255,
                             blank=True)
    description = models.CharField(null=True,
                                   default=None,
                                   max_length=255,
                                   blank=True)
    aggregation = models.CharField(null=True,
                                   default=None,
                                   max_length=255,
                                   blank=True)
    key = models.CharField(db_index=True, unique=True, max_length=32)

    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    order_with_respect_to = 'content_type'
    metadata = JSONField(default=dict, blank=True)

    class Meta(OrderedModel.Meta):
        app_label = 'logger'

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

        if not self.key:
            self.key = generate_uuid_for_form()

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

    @classmethod
    def query_data(cls, widget):
        # get the columns needed
        column = widget.column
        group_by = widget.group_by if widget.group_by else None

        if isinstance(widget.content_object, XForm):
            xform = widget.content_object
        elif isinstance(widget.content_object, DataView):
            xform = widget.content_object.xform

        field = get_field_from_field_xpath(column, xform)

        if isinstance(field, basestring) and field == SUBMISSION_TIME:
            field_label = 'Submission Time'
            field_xpath = '_submission_time'
            field_type = 'datetime'
            data_type = DATA_TYPE_MAP.get(field_type, 'categorized')
        else:
            field_type = field.type
            data_type = DATA_TYPE_MAP.get(field.type, 'categorized')
            field_xpath = field.get_abbreviated_xpath()
            field_label = get_field_label(field)

        columns = [
            SimpleField(field="json->>'%s'" % text(column),
                        alias='{}'.format(column)),
            CountField(field="json->>'%s'" % text(column), alias='count')
        ]

        if group_by:
            if field_type in NUMERIC_LIST:
                column_field = SimpleField(field="json->>'%s'" % text(column),
                                           cast="float",
                                           alias=column)
            else:
                column_field = SimpleField(field="json->>'%s'" % text(column),
                                           alias=column)

            # build inner query
            inner_query_columns = \
                [column_field,
                 SimpleField(field="json->>'%s'" % text(group_by),
                             alias=group_by),
                 SimpleField(field="xform_id"),
                 SimpleField(field="deleted_at")]
            inner_query = Query().from_table(Instance, inner_query_columns)

            # build group-by query
            if field_type in NUMERIC_LIST:
                columns = [
                    SimpleField(field=group_by, alias='%s' % group_by),
                    SumField(field=column, alias="sum"),
                    AvgField(field=column, alias="mean")
                ]
            elif field_type == SELECT_ONE:
                columns = [
                    SimpleField(field=column, alias='%s' % column),
                    SimpleField(field=group_by, alias='%s' % group_by),
                    CountField(field="*", alias='count')
                ]

            query = Query().from_table({'inner_query': inner_query}, columns).\
                where(xform_id=xform.pk, deleted_at=None)

            if field_type == SELECT_ONE:
                query.group_by(column).group_by(group_by)
            else:
                query.group_by(group_by)

        else:
            query = Query().from_table(Instance, columns).\
                where(xform_id=xform.pk, deleted_at=None)
            query.group_by("json->>'%s'" % text(column))

        # run query
        records = query.select()

        # flatten multiple dict if select one with group by
        if field_type == SELECT_ONE and group_by:
            records = _flatten_multiple_dict_into_one(column, group_by,
                                                      records)
        # use labels if group by
        if group_by:
            group_by_field = get_field_from_field_xpath(group_by, xform)
            choices = get_field_choices(group_by, xform)
            records = _use_labels_from_group_by_name(group_by,
                                                     group_by_field,
                                                     data_type,
                                                     records,
                                                     choices=choices)

        return {
            "field_type": field_type,
            "data_type": data_type,
            "field_xpath": field_xpath,
            "field_label": field_label,
            "grouped_by": group_by,
            "data": records
        }
Example #3
0
class Activity(models.Model):
    TYPES = ChoicesEnum(
        "TYPES",
        (
            "GUILD_CREATE",  # UNUSED
            # General guild detail changes
            "GUILD_UPDATE",
            # Discord integration changes
            "GUILD_UPDATE_INTEGRATION",
            "GUILD_DELETE",  # UNUSED
            "WAR_CREATE",
            # General war detail changes
            "WAR_UPDATE",
            "WAR_DELETE",
            # Finish war
            "WAR_END",
            # Attendance change by officer or higher
            "ATTENDANCE_UPDATE",
            # Stat Changes
            "WAR_STAT_CREATE",
            "WAR_STAT_DELETE",
            "WAR_STAT_UPDATE",
        ))

    date = models.DateTimeField(auto_now_add=True)
    type = models.IntegerField(choices=TYPES.choices())
    guild = models.ForeignKey("Guild", null=True, blank=True)
    actor_profile = models.ForeignKey("Profile")
    content_type = models.ForeignKey(ContentType,
                                     on_delete=models.CASCADE,
                                     null=True,
                                     blank=True)
    object_id = models.PositiveIntegerField(null=True, blank=True)
    target = GenericForeignKey('content_type', 'object_id')
    # Populated when target object is deleted
    target_description = models.CharField(max_length=255,
                                          null=True,
                                          blank=True)
    extras = JSONField(default={})

    class Meta:
        ordering = ('-date', 'id')

    def __str__(self):
        methodname = 'format_{0}'.format(
            self.TYPES._value2member_map_[self.type].name.lower())

        if hasattr(self, methodname):
            return getattr(self, methodname)()

        return str(self.type)

    def format_guild_create(self):
        return "{0} created the guild {1}".format(self.actor_profile,
                                                  self.target_description)

    def format_guild_update(self):
        return "{0} updated the guild {1}".format(self.actor_profile,
                                                  self.target_description)

    def format_guild_update_integration(self):
        return "{0} updated integration settings for the guild {1}".format(
            self.actor_profile, self.target_description)

    def format_guild_delete(self):
        return "{0} deleted the guild {1}".format(self.actor_profile,
                                                  self.target_description)

    def format_war_create(self):
        return "{0} started a war".format(self.actor_profile)

    def format_war_update(self):
        return "{0} updated war details".format(self.actor_profile)

    def format_war_delete(self):
        return "{0} cancelled the war {1}".format(self.actor_profile,
                                                  self.target_description)

    def format_war_end(self):
        return "{0} finished the war".format(self.actor_profile)

    def format_war_stat_create(self):
        return "{0} created war stat for {1}".format(self.actor_profile,
                                                     self.target_description)

    def format_war_stat_delete(self):
        return "{0} deleted war stat for {1}".format(self.actor_profile,
                                                     self.target_description)

    def format_war_stat_update(self):
        return "{0} updated war stat for {1}".format(self.actor_profile,
                                                     self.target_description)

    def format_attendance_update(self):
        return "{0} updated attendance for {1}".format(self.actor_profile,
                                                       self.target)
Example #4
0
class LikeNum(models.Model):
    like_num = models.IntegerField(default=0)
    content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
Example #5
0
class ReadNum(models.Model):
    read_num = models.IntegerField(default=0, verbose_name='阅读数量')
    content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
Example #6
0
class Sender(DynamicParent):
    sender = models.CharField(max_length=128)
    value = models.CharField(max_length=128)
    alias = models.CharField(max_length=128, blank=True)

    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        limit_choices_to=(models.Q(app_label='auth', model='user')
                          | models.Q(app_label='promgen', model='project')
                          | models.Q(app_label='promgen', model='service')))
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              on_delete=models.CASCADE,
                              null=True)

    def show_value(self):
        if self.alias:
            return self.alias
        return self.value

    show_value.short_description = 'Value'

    def __str__(self):
        return '{}:{}'.format(self.sender, self.show_value())

    @classmethod
    def driver_set(cls):
        '''Return the list of drivers for Sender model'''
        for entry in plugins.notifications():
            try:
                yield entry.module_name, entry.load()
            except ImportError:
                logger.warning('Error importing %s', entry.module_name)

    @property
    def driver(self):
        '''Return configured driver for Sender model instance'''
        for entry in plugins.notifications():
            if entry.module_name == self.sender:
                try:
                    return entry.load()()
                except ImportError:
                    logger.warning('Error importing %s', entry.module_name)

    def test(self):
        '''
        Test sender plugin

        Uses the same test json from our unittests but subs in the currently
        tested object as part of the test data
        '''
        data = tests.PromgenTest.data_json('examples', 'alertmanager.json')
        if hasattr(self.content_object, 'name'):
            data['commonLabels'][
                self.content_type.name] = self.content_object.name
            for alert in data.get('alerts', []):
                alert['labels'][
                    self.content_type.name] = self.content_object.name

        for entry in plugins.notifications():
            if entry.module_name == self.sender:
                plugin = entry.load()()
                plugin.test(self.value, data)
Example #7
0
class ObjectChange(models.Model):
    """
    Record a change done to an object and the user who did it.
    """

    time = models.DateTimeField(auto_now_add=True, editable=False)
    user = models.ForeignKey(
        to=User,
        on_delete=models.SET_NULL,
        related_name="changes",
        blank=True,
        null=True,
    )
    user_name = models.CharField(max_length=150, editable=False)
    request_id = models.UUIDField(editable=False)
    action = models.PositiveSmallIntegerField(
        choices=OBJECT_CHANGE_ACTION_CHOICES)
    changed_object_type = models.ForeignKey(to=ContentType,
                                            on_delete=models.PROTECT,
                                            related_name="+")
    changed_object_id = models.PositiveIntegerField()
    changed_object = GenericForeignKey(ct_field="changed_object_type",
                                       fk_field="changed_object_id")
    related_object_type = models.ForeignKey(
        to=ContentType,
        on_delete=models.PROTECT,
        related_name="+",
        blank=True,
        null=True,
    )
    related_object_id = models.PositiveIntegerField(blank=True, null=True)
    related_object = GenericForeignKey(ct_field="related_object_type",
                                       fk_field="related_object_id")
    object_repr = models.CharField(max_length=256, editable=False)
    object_data = JSONField(editable=False)

    class Meta:
        ordering = ["-time"]

    def save(self, *args, **kwargs):
        self.user_name = self.user.username
        self.object_repr = str(self.changed_object)

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

    def get_absolute_url(self):
        return reverse("utils:object_change_details", kwargs={"pk": self.pk})

    def get_html_icon(self):
        if self.action == OBJECT_CHANGE_ACTION_CREATE:
            return mark_safe('<i class="fas fa-plus-square text-success"></i>')
        if self.action == OBJECT_CHANGE_ACTION_UPDATE:
            return mark_safe('<i class="fas fa-pen-square text-warning"></i>')
        if self.action == OBJECT_CHANGE_ACTION_DELETE:
            return mark_safe('<i class="fas fa-minus-square text-danger"></i>')
        return mark_safe(
            '<i class="fas fa-question-circle text-secondary"></i>')

    def __str__(self):
        return "{} {} {} by {}".format(
            title_with_uppers(self.changed_object_type),
            self.object_repr,
            self.get_action_display().lower(),
            self.user_name,
        )
Example #8
0
class Allegato(ConMarcaTemporale, ConScadenzaPulizia, ModelloSemplice):
    """
    Rappresenta un allegato generico in database, con potenziale scadenza.
    """
    class Meta:
        verbose_name_plural = "Allegati"
        permissions = (("view_allegato", "Can view allegato"), )

    oggetto_tipo = models.ForeignKey(ContentType,
                                     db_index=True,
                                     blank=True,
                                     null=True,
                                     related_name="allegato_come_oggetto",
                                     on_delete=models.SET_NULL)
    oggetto_id = models.PositiveIntegerField(db_index=True,
                                             blank=True,
                                             null=True)
    oggetto = GenericForeignKey('oggetto_tipo', 'oggetto_id')
    file = models.FileField("File",
                            upload_to=GeneratoreNomeFile('allegati/'),
                            validators=[valida_dimensione_file_10mb])
    nome = models.CharField("Nome file",
                            max_length=255,
                            default="File",
                            blank=False,
                            null=False)

    @property
    def download_url(self):
        """
        Ritorna l'URL per il download del file.
        """
        return self.file.url

    def prepara_cartelle(self, file_path, path_include_file=True):
        """
        Crea ricorsivamente le directory per la creazione di un file.
        Se path_include_file non specificato o True, il primo parametro aspettato e' il path completo
        al nuovo file. Altrimenti, solo il percorso da creare e' aspettato.
        """
        if path_include_file:
            path = os.path.dirname(file_path)
        else:
            path = file_path

        if os.path.isdir(path):
            return True

        try:
            os.makedirs(path)
        except OSError as e:
            if e.errno == 17:
                # Dir already exists. No biggie.
                pass

        return True

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

    @property
    def filename(self):
        return os.path.basename(self.file.name)
Example #9
0
class Like(models.Model):
    owner = models.ForeignKey(User, related_name='new_likes')

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
Example #10
0
class FlexCategory(models.Model):
    name = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
Example #11
0
class Autorizzazione(ModelloSemplice, ConMarcaTemporale):
    """
    Rappresenta una richiesta di autorizzazione relativa ad un oggetto generico.
    Utilizzare tramite il tratto ConAutorizzazioni ed i suoi metodi.
    """

    AP_AUTO = "A"
    NG_AUTO = "N"
    MANUALE = "M"
    AUTOMATICA = "T"

    TIPO_GESTIONE = (
        (MANUALE, "Manuale"),
        (AP_AUTO, "Approvazione automatica"),
        (NG_AUTO, "Negazione automatica"),
    )

    PROTOCOLLO_AUTO = "AUTO"  # Applicato a protocollo_numero se approvazione automatica

    richiedente = models.ForeignKey("anagrafica.Persona",
                                    db_index=True,
                                    related_name="autorizzazioni_richieste",
                                    on_delete=models.CASCADE)
    firmatario = models.ForeignKey("anagrafica.Persona",
                                   db_index=True,
                                   blank=True,
                                   null=True,
                                   default=None,
                                   related_name="autorizzazioni_firmate",
                                   on_delete=models.SET_NULL)
    concessa = models.NullBooleanField("Esito",
                                       db_index=True,
                                       blank=True,
                                       null=True,
                                       default=None)
    motivo_negazione = models.CharField(blank=True, null=True, max_length=512)

    oggetto_tipo = models.ForeignKey(ContentType,
                                     db_index=True,
                                     related_name="autcomeoggetto",
                                     on_delete=models.SET_NULL,
                                     null=True)
    oggetto_id = models.PositiveIntegerField(db_index=True)
    oggetto = GenericForeignKey('oggetto_tipo', 'oggetto_id')

    necessaria = models.BooleanField("Necessaria", db_index=True, default=True)
    progressivo = models.PositiveSmallIntegerField("Progressivo contesto",
                                                   default=1)

    destinatario_ruolo = models.CharField(max_length=16,
                                          choices=INCARICHI,
                                          db_index=True)
    destinatario_oggetto_tipo = models.ForeignKey(
        ContentType,
        db_index=True,
        related_name="autcomedestinatari",
        null=True,
        on_delete=models.SET_NULL)
    destinatario_oggetto_id = models.PositiveIntegerField(db_index=True)
    destinatario_oggetto = GenericForeignKey('destinatario_oggetto_tipo',
                                             'destinatario_oggetto_id')
    scadenza = models.DateTimeField(blank=True, null=True, db_index=True)
    tipo_gestione = models.CharField(default=MANUALE,
                                     max_length=1,
                                     choices=TIPO_GESTIONE)
    automatica = models.BooleanField("Approvata automaticamente",
                                     default=False,
                                     db_index=True)

    @property
    def giorni_automatici(self):
        if self.scadenza:
            return (self.scadenza - self.creazione).days
        else:
            return None

    def firma(self,
              firmatario,
              concedi=True,
              modulo=None,
              motivo=None,
              auto=False,
              notifiche_attive=True,
              data=None):
        """
        Firma l'autorizzazione.
        :param firmatario: Il firmatario.
        :param concedi: L'esito, vero per concedere, falso per negare.
        :param modulo: Se modulo necessario, un modulo valido.
        :param auto: Se la firma avviene con procedura automatica / massiva
        :param notifiche_attive: Se inviare notifiche
        :return:
        """
        # Controlla che il modulo fornito, se presente, sia valido
        if modulo and not modulo.is_valid():
            raise ValueError(
                "Il modulo richiesto per l'accettazione non e' stato completato correttamente."
            )

        if not self.oggetto:
            print("L'autorizzazione %s risulta non avere oggetti collegati" %
                  self.pk)
            return

        self.concessa = concedi
        self.firmatario = firmatario
        self.necessaria = False
        if firmatario and not auto:
            self.automatica = False
        elif auto:
            self.automatica = True
        self.save()

        # Se ha negato, allora avvisa subito della negazione.
        # Nessuna altra firma e' piu' necessaria.
        if not concedi:
            self.oggetto.confermata = False
            self.oggetto.save()
            self.oggetto.autorizzazione_negata(modulo=modulo, data=None)
            if modulo:
                if 'motivo' in modulo.cleaned_data:
                    self.motivo_negazione = modulo.cleaned_data['motivo']
                    self.save()
            self.oggetto.autorizzazioni_set().update(necessaria=False)
            if self.oggetto.INVIA_NOTIFICA_NEGATA and notifiche_attive:
                self.notifica_negata(auto=auto)
            return

        # Questa concessa, di questo progressivo non e' piu' necessaria
        # alcuna auutorizzazione.
        self.oggetto.autorizzazioni_set().filter(
            progressivo=self.progressivo).update(necessaria=False)

        # Se questa autorizzazione e' concessa, ed e' l'ultima.
        if self.oggetto.autorizzazioni_set().filter(
                necessaria=True).count() == 0:
            self.oggetto.confermata = True
            self.oggetto.save()
            self.oggetto.autorizzazione_concessa(
                modulo=modulo,
                auto=auto,
                notifiche_attive=notifiche_attive,
                data=data)
            if self.oggetto.INVIA_NOTIFICA_CONCESSA and notifiche_attive:
                self.notifica_concessa(auto=auto)

    def concedi(self,
                firmatario=None,
                modulo=None,
                auto=False,
                notifiche_attive=True,
                data=None):
        self.firma(firmatario,
                   True,
                   modulo=modulo,
                   auto=auto,
                   notifiche_attive=notifiche_attive,
                   data=data)

    def nega(self,
             firmatario=None,
             modulo=None,
             auto=False,
             notifiche_attive=True,
             data=None):
        self.firma(firmatario,
                   False,
                   modulo=modulo,
                   auto=auto,
                   notifiche_attive=notifiche_attive,
                   data=data)

    @property
    def template_path(self):
        """
        Ritorna il nome del template dell'autorizzazione.
        """
        return 'base_autorizzazioni_inc_%s_%s.html' % (
            self.oggetto._meta.app_label.lower(),
            self.oggetto._meta.object_name.lower())

    @staticmethod
    def espandi_notifiche(sede,
                          invia_notifiche,
                          invia_notifica_presidente=False,
                          invia_notifica_ufficio_soci=False):

        if invia_notifica_presidente:
            presidente = sede.presidente()
            invia_notifiche = list(invia_notifiche) + ([presidente]
                                                       if presidente else [])

        if invia_notifica_ufficio_soci:
            ufficio_soci = list(sede.delegati_ufficio_soci())
            invia_notifiche = list(invia_notifiche) + ufficio_soci
        return list(set(invia_notifiche))

    def notifica_richiesta(self, persona):
        from anagrafica.models import Delega, Persona
        from posta.models import Messaggio

        if not persona:
            return  # Nessun destinatario, nessuna e-mail.

        Messaggio.costruisci_e_accoda(
            oggetto="Richiesta di %s da %s" % (
                self.oggetto.RICHIESTA_NOME,
                self.richiedente.nome_completo,
            ),
            modello="email_autorizzazione_richiesta.html",
            corpo={
                "richiesta": self,
            },
            mittente=self.richiedente,
            destinatari=[persona],
        )

    def notifica_sede_autorizzazione_concessa(self, sede, testo_extra=''):
        """
        Notifica presidente e ufficio soci del comitato
        di origine dell'avvenuta approvazione della richiesta.
        """

        notifiche = self.espandi_notifiche(sede, [], True, True)
        modello = "email_autorizzazione_concessa_notifica_origine.html"
        oggetto = "Richiesta di %s da %s APPROVATA" % (
            self.oggetto.RICHIESTA_NOME,
            self.richiedente.nome_completo,
        )
        aggiunte_corpo = {
            'testo_extra': testo_extra,
            'firmatario': self.firmatario,
            'automatica': self.automatica,
        }
        self._invia_notifica(modello,
                             oggetto,
                             False,
                             destinatari=notifiche,
                             aggiunte_corpo=aggiunte_corpo)

    def _invia_notifica(self,
                        modello,
                        oggetto,
                        auto,
                        destinatari=None,
                        aggiunte_corpo=None):
        from posta.models import Messaggio

        if not destinatari:
            if auto:
                destinatari = [self.richiedente]
                if self.firmatario:
                    destinatari.append(self.firmatario)
                self.oggetto.automatica = True
                self.oggetto.save()
            else:
                destinatari = [self.richiedente]
        corpo = {
            "richiesta": self,
            "firmatario": self.firmatario,
            "giorni": self.giorni_automatici,
        }
        if aggiunte_corpo:
            corpo.update(aggiunte_corpo)

        Messaggio.costruisci_e_accoda(oggetto=oggetto,
                                      modello=modello,
                                      corpo=corpo,
                                      mittente=self.firmatario,
                                      destinatari=destinatari)

    def notifica_concessa(self, auto=False):
        if auto:
            modello = "email_autorizzazione_concessa_automatica.html"
        else:
            modello = "email_autorizzazione_concessa.html"
        oggetto = "Richiesta di %s APPROVATA" % (self.oggetto.RICHIESTA_NOME, )
        self._invia_notifica(modello, oggetto, auto)

    def notifica_negata(self, auto=False):
        if auto:
            modello = "email_autorizzazione_negata_automatica.html"
        else:
            modello = "email_autorizzazione_negata.html"
        oggetto = "Richiesta di %s RESPINTA" % (self.oggetto.RICHIESTA_NOME, )
        aggiunte_corpo = {'declined_at': datetime.now()}
        self._invia_notifica(modello,
                             oggetto,
                             auto,
                             aggiunte_corpo=aggiunte_corpo)

    @property
    def is_valid_per_approvazione_automatica(self):
        if self.oggetto is None:
            return False  # Saltare Autorizzazione senza <oggetto>
        else:
            return self.scadenza \
                   and self.concessa is None \
                   and self.scadenza < now() \
                   and not self.oggetto.ritirata

    def controlla_concedi_automatico(self):
        if self.is_valid_per_approvazione_automatica:
            self.concedi(auto=True)

    def controlla_nega_automatico(self):
        if self.is_valid_per_approvazione_automatica:
            self.nega(auto=True)

    @classmethod
    def gestisci_automatiche(cls):
        delta = now() - relativedelta(months=1)
        qs = cls.objects.filter(concessa__isnull=True,
                                scadenza__isnull=False,
                                scadenza__gte=delta,
                                scadenza__lte=now())
        da_negare = qs.filter(tipo_gestione=cls.NG_AUTO)
        da_approvare = qs.filter(tipo_gestione=cls.AP_AUTO)

        for autorizzazione in da_negare:
            autorizzazione.controlla_nega_automatico()
        for autorizzazione in da_approvare:
            autorizzazione.controlla_concedi_automatico()

    def automatizza(self, concedi=None, scadenza=None):
        if not self.oggetto:
            print('Autorizzazione %s non ha oggetto collegato' %
                  autorizzazione.pk)
            return

        if concedi and not self.oggetto.ritirata:
            self.tipo_gestione = concedi
            self.scadenza = calcola_scadenza(scadenza)
            self.save()

    @classmethod
    def notifiche_richieste_in_attesa(cls):
        from anagrafica.models import Estensione, Trasferimento
        from posta.models import Messaggio

        oggetto = "Richieste in attesa di approvazione"
        modello = "email_richieste_pending.html"

        in_attesa = cls.objects.filter(concessa__isnull=True)
        trasferimenti = in_attesa.filter(
            oggetto_tipo=ContentType.objects.get_for_model(Trasferimento))
        estensioni = in_attesa.filter(
            oggetto_tipo=ContentType.objects.get_for_model(Estensione))
        trasferimenti_manuali = trasferimenti.filter(
            scadenza__isnull=True, tipo_gestione=Autorizzazione.MANUALE)
        trasferimenti_automatici = trasferimenti.filter(
            scadenza__isnull=False,
            scadenza__gt=now()).exclude(tipo_gestione=Autorizzazione.MANUALE)

        autorizzazioni = list(estensioni) + list(trasferimenti_manuali) + list(
            trasferimenti_automatici)

        persone = dict()
        for autorizzazione in autorizzazioni:
            if not autorizzazione.oggetto:
                print('Autorizzazione %s non ha oggetto collegato' %
                      autorizzazione.pk)
                continue
            if autorizzazione.oggetto and not autorizzazione.oggetto.ritirata and not autorizzazione.oggetto.confermata:
                destinatari = cls.espandi_notifiche(
                    autorizzazione.destinatario_oggetto, [], True, True)
                for destinatario in destinatari:
                    if destinatario.pk not in persone:
                        persone[destinatario.pk] = {
                            'persona': None,
                            'estensioni': [],
                            'trasferimenti_manuali': [],
                            'trasferimenti_automatici': [],
                        }
                    persone[destinatario.pk]['persona'] = destinatario
                    if autorizzazione in estensioni:
                        persone[destinatario.pk]['estensioni'].append(
                            autorizzazione.oggetto)
                    elif autorizzazione in trasferimenti_manuali:
                        persone[
                            destinatario.pk]['trasferimenti_manuali'].append(
                                autorizzazione.oggetto)
                    elif autorizzazione in trasferimenti_automatici:
                        persone[destinatario.
                                pk]['trasferimenti_automatici'].append(
                                    autorizzazione.oggetto)

        for persona in persone.values():
            corpo = {
                "persona":
                persona,
                "DATA_AVVIO_TRASFERIMENTI_AUTO":
                settings.DATA_AVVIO_TRASFERIMENTI_AUTO
            }

            Messaggio.costruisci_e_accoda(oggetto=oggetto,
                                          modello=modello,
                                          corpo=corpo,
                                          destinatari=[persona['persona']])

    class Meta:
        verbose_name_plural = "Autorizzazioni"
        app_label = "base"
        index_together = [
            ['necessaria', 'progressivo'],
            ['necessaria', 'concessa'],
            [
                'destinatario_ruolo',
                'destinatario_oggetto_tipo',
            ],
            [
                'necessaria', 'destinatario_ruolo',
                'destinatario_oggetto_tipo', 'destinatario_oggetto_id'
            ],
            [
                'destinatario_ruolo', 'destinatario_oggetto_tipo',
                'destinatario_oggetto_id'
            ],
            ['destinatario_oggetto_tipo', 'destinatario_oggetto_id'],
            [
                'necessaria', 'destinatario_oggetto_tipo',
                'destinatario_oggetto_id'
            ],
            [
                'necessaria', 'destinatario_ruolo',
                'destinatario_oggetto_tipo', 'destinatario_oggetto_id'
            ],
        ]
        permissions = (("view_autorizzazione", "Can view autorizzazione"), )

    def __str__(self):
        if self.oggetto:
            return str(self.oggetto)
        else:
            return super().__str__()
Example #12
0
class Cable(PrimaryModel, StatusModel):
    """
    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.UUIDField()
    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.UUIDField()
    termination_b = GenericForeignKey(ct_field="termination_b_type", fk_field="termination_b_id")
    type = models.CharField(max_length=50, choices=CableTypeChoices, blank=True)
    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
    )

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

    class Meta:
        ordering = [
            "termination_a_type",
            "termination_a_id",
            "termination_b_type",
            "termination_b_id",
        ]
        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])

    @classproperty
    def STATUS_CONNECTED(cls):
        """Return a cached "connected" `Status` object for later reference."""
        if getattr(cls, "__status_connected", None) is None:
            cls.__status_connected = Status.objects.get_for_model(Cable).get(slug="connected")
        return cls.__status_connected

    def clean(self):
        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.present_in_database:
            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_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]
Example #13
0
class CablePath(BaseModel):
    """
    A CablePath instance represents the physical path from an origin to a destination, including all intermediate
    elements in the path. Every instance must specify an `origin`, whereas `destination` may be null (for paths which do
    not terminate on a PathEndpoint).

    `path` contains a list of nodes within the path, each represented by a tuple of (type, ID). The first element in the
    path must be a Cable instance, followed by a pair of pass-through ports. For example, consider the following
    topology:

                     1                              2                              3
        Interface A --- Front Port A | Rear Port A --- Rear Port B | Front Port B --- Interface B

    This path would be expressed as:

    CablePath(
        origin = Interface A
        destination = Interface B
        path = [Cable 1, Front Port A, Rear Port A, Cable 2, Rear Port B, Front Port B, Cable 3]
    )

    `is_active` is set to True only if 1) `destination` is not null, and 2) every Cable within the path has a status of
    "connected".
    """

    origin_type = models.ForeignKey(to=ContentType, on_delete=models.CASCADE, related_name="+")
    origin_id = models.UUIDField()
    origin = GenericForeignKey(ct_field="origin_type", fk_field="origin_id")
    destination_type = models.ForeignKey(
        to=ContentType,
        on_delete=models.CASCADE,
        related_name="+",
        blank=True,
        null=True,
    )
    destination_id = models.UUIDField(blank=True, null=True)
    destination = GenericForeignKey(ct_field="destination_type", fk_field="destination_id")
    path = PathField()
    is_active = models.BooleanField(default=False)
    is_split = models.BooleanField(default=False)

    class Meta:
        unique_together = ("origin_type", "origin_id")

    def __str__(self):
        status = " (active)" if self.is_active else " (split)" if self.is_split else ""
        return f"Path #{self.pk}: {self.origin} to {self.destination} via {len(self.path)} nodes{status}"

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

        # Record a direct reference to this CablePath on its originating object
        model = self.origin._meta.model
        model.objects.filter(pk=self.origin.pk).update(_path=self.pk)

    @property
    def segment_count(self):
        total_length = 1 + len(self.path) + (1 if self.destination else 0)
        return int(total_length / 3)

    @classmethod
    def from_origin(cls, origin):
        """
        Create a new CablePath instance as traced from the given path origin.
        """
        if origin is None or origin.cable is None:
            return None

        # Import added here to avoid circular imports with Cable.
        from nautobot.circuits.models import CircuitTermination

        destination = None
        path = []
        position_stack = []
        is_active = True
        is_split = False

        node = origin
        while node.cable is not None:
            if node.cable.status != Cable.STATUS_CONNECTED:
                is_active = False

            # Follow the cable to its far-end termination
            path.append(object_to_path_node(node.cable))
            peer_termination = node.get_cable_peer()

            # Follow a FrontPort to its corresponding RearPort
            if isinstance(peer_termination, FrontPort):
                path.append(object_to_path_node(peer_termination))
                node = peer_termination.rear_port
                if node.positions > 1:
                    position_stack.append(peer_termination.rear_port_position)
                path.append(object_to_path_node(node))

            # Follow a RearPort to its corresponding FrontPort (if any)
            elif isinstance(peer_termination, RearPort):
                path.append(object_to_path_node(peer_termination))

                # Determine the peer FrontPort's position
                if peer_termination.positions == 1:
                    position = 1
                elif position_stack:
                    position = position_stack.pop()
                else:
                    # No position indicated: path has split, so we stop at the RearPort
                    is_split = True
                    break

                try:
                    node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=position)
                    path.append(object_to_path_node(node))
                except ObjectDoesNotExist:
                    # No corresponding FrontPort found for the RearPort
                    break

            # Follow a Circuit Termination if there is a corresponding Circuit Termination
            # Side A and Side Z exist
            elif isinstance(peer_termination, CircuitTermination):
                node = peer_termination.get_peer_termination()
                # A Circuit Termination does not require a peer.
                if node is None:
                    destination = peer_termination
                    break
                path.append(object_to_path_node(peer_termination))
                path.append(object_to_path_node(node))

            # Anything else marks the end of the path
            else:
                destination = peer_termination
                break

        if destination is None:
            is_active = False

        return cls(
            origin=origin,
            destination=destination,
            path=path,
            is_active=is_active,
            is_split=is_split,
        )

    def get_path(self):
        """
        Return the path as a list of prefetched objects.
        """
        # Compile a list of IDs to prefetch for each type of model in the path
        to_prefetch = defaultdict(list)
        for node in self.path:
            ct_id, object_id = decompile_path_node(node)
            to_prefetch[ct_id].append(object_id)

        # Prefetch path objects using one query per model type. Prefetch related devices where appropriate.
        prefetched = {}
        for ct_id, object_ids in to_prefetch.items():
            model_class = ContentType.objects.get_for_id(ct_id).model_class()
            queryset = model_class.objects.filter(pk__in=object_ids)
            if hasattr(model_class, "device"):
                queryset = queryset.prefetch_related("device")
            prefetched[ct_id] = {obj.id: obj for obj in queryset}

        # Replicate the path using the prefetched objects.
        path = []
        for node in self.path:
            ct_id, object_id = decompile_path_node(node)
            path.append(prefetched[ct_id][object_id])

        return path

    def get_total_length(self):
        """
        Return the sum of the length of each cable in the path.
        """
        cable_ids = [
            # Starting from the first element, every third element in the path should be a Cable
            decompile_path_node(self.path[i])[1]
            for i in range(0, len(self.path), 3)
        ]
        return Cable.objects.filter(id__in=cable_ids).aggregate(total=Sum("_abs_length"))["total"]

    def get_split_nodes(self):
        """
        Return all available next segments in a split cable path.
        """
        rearport = path_node_to_object(self.path[-1])
        return FrontPort.objects.filter(rear_port=rearport)
Example #14
0
class ObjectChange(models.Model):
    """
    Record a change to an object and the user account associated with that change. A change record may optionally
    indicate an object related to the one being changed. For example, a change to an interface may also indicate the
    parent device. This will ensure changes made to component models appear in the parent model's changelog.
    """
    time = models.DateTimeField(auto_now_add=True,
                                editable=False,
                                db_index=True)
    user = models.ForeignKey(to=User,
                             on_delete=models.SET_NULL,
                             related_name='changes',
                             blank=True,
                             null=True)
    user_name = models.CharField(max_length=150, editable=False)
    request_id = models.UUIDField(editable=False)
    action = models.CharField(max_length=50, choices=ObjectChangeActionChoices)
    changed_object_type = models.ForeignKey(to=ContentType,
                                            on_delete=models.PROTECT,
                                            related_name='+')
    changed_object_id = models.PositiveIntegerField()
    changed_object = GenericForeignKey(ct_field='changed_object_type',
                                       fk_field='changed_object_id')
    related_object_type = models.ForeignKey(to=ContentType,
                                            on_delete=models.PROTECT,
                                            related_name='+',
                                            blank=True,
                                            null=True)
    related_object_id = models.PositiveIntegerField(blank=True, null=True)
    related_object = GenericForeignKey(ct_field='related_object_type',
                                       fk_field='related_object_id')
    object_repr = models.CharField(max_length=200, editable=False)
    object_data = JSONField(editable=False)

    csv_headers = [
        'time',
        'user',
        'user_name',
        'request_id',
        'action',
        'changed_object_type',
        'changed_object_id',
        'related_object_type',
        'related_object_id',
        'object_repr',
        'object_data',
    ]

    class Meta:
        ordering = ['-time']

    def __str__(self):
        return '{} {} {} by {}'.format(self.changed_object_type,
                                       self.object_repr,
                                       self.get_action_display().lower(),
                                       self.user_name)

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

        # Record the user's name and the object's representation as static strings
        if not self.user_name:
            self.user_name = self.user.username
        if not self.object_repr:
            self.object_repr = str(self.changed_object)

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

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

    def to_csv(self):
        return (
            self.time,
            self.user,
            self.user_name,
            self.request_id,
            self.get_action_display(),
            self.changed_object_type,
            self.changed_object_id,
            self.related_object_type,
            self.related_object_id,
            self.object_repr,
            self.object_data,
        )
Example #15
0
class Criterion(models.Model):
    """
    Base class for all criteria.

    **Attributes:**

    cart
        The current cart of the current customer.

    content
        The content object the criterion belongs to.

    operator
        The current selected operator for the criterion.

    position
        The position of the criterion within a list of criteria of the
        content object.

    product
        The product, if the criterion is called from a product detail view.
        Otherwise this is None.

    request
        The current request.

    **Constants:**

    EQUAL, LESS_THAN, LESS_THAN_EQUAL, GREATER_THAN, GREATER_THAN_EQUAL, IS_SELECTED, IS_NOT_SELECTED, IS_VALID, IS_NOT_VALID, CONTAINS
        Integers which represents certain operators.

    INPUT, SELECT, MULTIPLE_SELECT
        Constants which represents the types of selectable values. One of
        these must be returned from ``get_value_type``.

    NUMBER_OPERATORS
        A list of operators which can be returned from ``get_operators``.

        .. code-block:: python

            [
                [EQUAL, _(u"Equal to")],
                [LESS_THAN, _(u"Less than")],
                [LESS_THAN_EQUAL, _(u"Less than equal to")],
                [GREATER_THAN, _(u"Greater than")],
                [GREATER_THAN_EQUAL, _(u"Greater than equal to")],
            ]


    SELECTION_OPERATORS
        A list of operators which can be returned from ``get_operators``.

        .. code-block:: python

            [
                [IS_SELECTED, _(u"Is selected")],
                [IS_NOT_SELECTED, _(u"Is not selected")],
            ]

    VALID_OPERATORS
        A list of operators which can be returned from ``get_operators``.

        .. code-block:: python

            [
                [IS_VALID, _(u"Is valid")],
                [IS_NOT_VALID, _(u"Is not valid")],
            ]

    STRING_OPERATORS
        A list of operators which can be return from ``get_operators``.

        .. code-block:: python

            [
                [EQUAL, _(u"Equal to")],
                [CONTAINS, _(u"Contains")],
            ]
    """
    content_type = models.ForeignKey(ContentType,
                                     models.CASCADE,
                                     verbose_name=_(u"Content type"),
                                     related_name="content_type")
    content_id = models.PositiveIntegerField(_(u"Content id"))
    content = GenericForeignKey(ct_field="content_type", fk_field="content_id")
    sub_type = models.CharField(_(u"Sub type"), max_length=100, blank=True)

    position = models.PositiveIntegerField(_(u"Position"), default=999)
    operator = models.PositiveIntegerField(_(u"Operator"),
                                           blank=True,
                                           null=True)

    class Meta:
        ordering = ("position", )
        app_label = 'criteria'

    EQUAL = 0
    LESS_THAN = 1
    LESS_THAN_EQUAL = 2
    GREATER_THAN = 3
    GREATER_THAN_EQUAL = 4
    IS_SELECTED = 10
    IS_NOT_SELECTED = 11
    IS_VALID = 21
    IS_NOT_VALID = 22
    CONTAINS = 32

    INPUT = 0
    SELECT = 1
    MULTIPLE_SELECT = 2

    NUMBER_OPERATORS = [
        [EQUAL, _(u"Equal to")],
        [LESS_THAN, _(u"Less than")],
        [LESS_THAN_EQUAL, _(u"Less than equal to")],
        [GREATER_THAN, _(u"Greater than")],
        [GREATER_THAN_EQUAL, _(u"Greater than equal to")],
    ]

    SELECTION_OPERATORS = [
        [IS_SELECTED, _(u"Is selected")],
        [IS_NOT_SELECTED, _(u"Is not selected")],
    ]

    VALID_OPERATORS = [
        [IS_VALID, _(u"Is valid")],
        [IS_NOT_VALID, _(u"Is not valid")],
    ]

    STRING_OPERATORS = [
        [EQUAL, _(u"Equal to")],
        [CONTAINS, _(u"Contains")],
    ]

    def __str__(self):
        """ We're using force unicode as this basically fails:
               from django.utils import translation
               from django.utils.translation import ugettext_lazy as _
               translation.activate('pl')
               u'test: %s' % _('Payment method')
        """
        return ugettext(
            "%(name)s: %(operator)s %(value)s" % {
                'name': force_text(self.get_name()),
                'operator': force_text(self.get_current_operator_as_string()),
                'value': force_text(self.get_value_as_string())
            })

    def save(self, *args, **kwargs):
        if self.sub_type == "":
            self.sub_type = self.__class__.__name__.lower()
        super(Criterion, self).save(*args, **kwargs)

    @property
    def cart(self):
        """
        Returns the current cart of the current customer.
        """
        return lfs.cart.utils.get_cart(self.request)

    def get_content_object(self):
        """
        Returns the specific content object of the criterion.

        This can be call on Criterion instances to get the specific criterion
        instance.
        """
        if self.__class__.__name__.lower() == "criterion":
            return getattr(self, self.sub_type)
        else:
            return self

    def get_current_operator_as_string(self):
        """
        Returns the current operator as string.
        """
        for operator in self.get_operators():
            if self.operator == operator[0]:
                return operator[1]

    def get_name(self):
        """
        Returns the name of the criterion as string.
        """
        klass = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
        for x in settings.LFS_CRITERIA:
            if x[0] == klass:
                return x[1]
        return self.__class__.__name__

    def get_operators(self):
        """
        Returns the selectable operators of the criterion which are displayed to
        the shop manager. This is a list of list, whereas the the first value is
        integer, which is stored within the criterion and the second value is
        the string which is displayed to the shop manager, e.g.:

        .. code-block:: python

            [
                [0, _(u"Equal to")],
                [1, _(u"Less than")],
                [2, _(u"Less than equal to")],
                [3, _(u"Greater than")],
                [4, _(u"Greater than equal to")],
            ]

        .. note::

            You can use one of the provided class attributes, see above.

            * NUMBER_OPERATORS
            * SELECTION_OPERATORS
            * VALID_OPERATORS
            * STRING_OPERATORS
        """
        raise NotImplementedError

    def get_selectable_values(self, request):
        """
        Returns the selectable values as a list of dictionary:

            [
                {
                    "id": 0,
                    "name": "Name 0",
                    "selected": False,
                },
                {
                    "id": 1,
                    "name": "Name 1",
                    "selected": True,
                },
            ]

        """
        return []

    def get_template(self, request):
        """
        Returns the template to render the criterion.
        """
        return "manage/criteria/base.html"

    def get_value(self):
        """
        Returns the current value of the criterion.
        """
        return self.value

    def get_value_type(self):
        """
        Returns the type of the selectable values field. Must return one of:

            * self.INPUT
            * self.SELECT
            * self.MULTIPLE_SELECT
        """
        return self.INPUT

    def get_value_as_string(self):
        """
        Returns the current value of the criterion as string.
        """
        value = self.get_value()

        if value.__class__.__name__ == "ManyRelatedManager":
            values = []
            for value in self.get_value().all():
                values.append(value.name)
            return ", ".join(values)
        else:
            return value

    def is_valid(self):
        """
        Returns ``True`` if the criterion is valid otherwise ``False``.
        """
        raise NotImplementedError

    def render(self, request, position):
        """
        Renders the criterion as html in order to displayed it within the
        management form.
        """
        operators = []
        for operator in self.get_operators():
            if self.operator == operator[0]:
                selected = True
            else:
                selected = False

            operators.append({
                "id": operator[0],
                "name": operator[1].encode("utf-8"),
                "selected": selected,
            })

        criteria = []
        for criterion in settings.LFS_CRITERIA:
            klass = criterion[0].split(".")[-1]
            if self.__class__.__name__ == klass:
                selected = True
            else:
                selected = False

            criteria.append({
                "module": criterion[0],
                "name": criterion[1],
                "selected": selected,
            })

        if self.id:
            id = "ex%s" % self.id
        else:
            id = timezone.now().microsecond

        return render_to_string(self.get_template(request),
                                request=request,
                                context={
                                    "id":
                                    id,
                                    "operator":
                                    self.operator,
                                    "value":
                                    self.get_value(),
                                    "position":
                                    position,
                                    "operators":
                                    operators,
                                    "criteria":
                                    criteria,
                                    "selectable_values":
                                    self.get_selectable_values(request),
                                    "value_type":
                                    self.get_value_type(),
                                    "criterion":
                                    self,
                                })

    def update(self, value):
        """
        Updates the value of the criterion.

        **Parameters:**

        value
            The value the shop user has entered for the criterion.
        """
        if isinstance(self.value, float):
            try:
                value = float(value)
            except (ValueError, TypeError):
                value = 0.0
            self.value = value
        elif isinstance(self.value, int):
            try:
                value = int(value)
            except (ValueError, TypeError):
                value = 0
            self.value = value
        elif self.value.__class__.__name__ == "ManyRelatedManager":
            for value_id in value:
                self.value.add(value_id)
        else:
            self.value = value

        self.save()
Example #16
0
class Award(models.Model):
    name = models.CharField(max_length=25)
    object_id = models.PositiveIntegerField()
    content_type = models.ForeignKey(ContentType, models.CASCADE)
    content_object = GenericForeignKey()
Example #17
0
class Rule(DynamicParent):
    name = models.CharField(max_length=128,
                            unique=True,
                            validators=[validators.alphanumeric])
    clause = models.TextField(help_text='Prometheus query')
    duration = models.CharField(
        max_length=128,
        validators=[validators.prometheusduration],
        help_text="Duration field with postfix. Example 30s, 5m, 1d")
    enabled = models.BooleanField(default=True)
    parent = models.ForeignKey('Rule',
                               null=True,
                               related_name='overrides',
                               on_delete=models.SET_NULL)

    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        limit_choices_to=(models.Q(app_label='sites', model='site')
                          | models.Q(app_label='promgen', model='project')
                          | models.Q(app_label='promgen', model='service')))
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    description = models.TextField(blank=True)

    class Meta:
        ordering = ['content_type', 'object_id', 'name']

    @cached_property
    def labels(self):
        return {obj.name: obj.value for obj in self.rulelabel_set.all()}

    def add_label(self, name, value):
        return RuleLabel.objects.get_or_create(rule=self,
                                               name=name,
                                               value=value)

    def add_annotation(self, name, value):
        return RuleAnnotation.objects.get_or_create(rule=self,
                                                    name=name,
                                                    value=value)

    @cached_property
    def annotations(self):
        _annotations = {
            obj.name: obj.value
            for obj in self.ruleannotation_set.all()
        }
        # Skip when pk is not set, such as when test rendering a rule
        if self.pk and 'rule' not in _annotations:
            _annotations['rule'] = resolve_domain('rule-edit', pk=self.pk)
        return _annotations

    def __str__(self):
        return '{} [{}]'.format(self.name, self.content_object.name)

    def get_absolute_url(self):
        return reverse('rule-edit', kwargs={'pk': self.pk})

    def set_object(self, content_type, object_id):
        self.content_type = ContentType.objects.get(model=content_type,
                                                    app_label='promgen')
        self.object_id = object_id

    def copy_to(self, content_type, object_id):
        '''
        Make a copy under a new service

        It's important that we set pk to None so a new object is created, but we
        also need to ensure the new name is unique by appending some unique data
        to the end of the name
        '''
        with transaction.atomic():
            content_type = ContentType.objects.get(model=content_type,
                                                   app_label='promgen')

            # First check to see if this rule is already overwritten
            for rule in Rule.objects.filter(parent_id=self.pk,
                                            content_type=content_type,
                                            object_id=object_id):
                return rule

            content_object = content_type.get_object_for_this_type(
                pk=object_id)

            orig_pk = self.pk
            self.pk = None
            self.parent_id = orig_pk
            self.name = '{}_{}'.format(self.name,
                                       slugify(content_object.name)).replace(
                                           '-', '_')
            self.content_type = content_type
            self.object_id = object_id
            self.enabled = False
            self.clause = self.clause.replace(
                macro.EXCLUSION_MACRO,
                '{}="{}",{}'.format(content_type.model, content_object.name,
                                    macro.EXCLUSION_MACRO))
            self.save()

            # Add a label to our new rule by default, to help ensure notifications
            # get routed to the notifier we expect
            self.add_label(content_type.model, content_object.name)

            for label in RuleLabel.objects.filter(rule_id=orig_pk):
                # Skip service labels from our previous rule
                if label.name in ['service', 'project']:
                    logger.debug('Skipping %s: %s', label.name, label.value)
                    continue
                logger.debug('Copying %s to %s', label, self)
                label.pk = None
                label.rule = self
                label.save()

            for annotation in RuleAnnotation.objects.filter(rule_id=orig_pk):
                logger.debug('Copying %s to %s', annotation, self)
                annotation.pk = None
                annotation.rule = self
                annotation.save()

        return self
Example #18
0
class ReadDetail(models.Model):
    date = models.DateField(default=timezone.now)
    read_num = models.IntegerField(default=0)  # 阅读量
    content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
Example #19
0
class LogEntry(models.Model):
    """
    Represents a change or action that has been performed on another object
    in the database. This uses django.contrib.contenttypes to allow a
    relation to an arbitrary database object.

    :param datetime: The timestamp of the logged action
    :type datetime: datetime
    :param user: The user that performed the action
    :type user: User
    :param action_type: The type of action that has been performed. This is
       used to look up the renderer used to describe the action in a human-
       readable way. This should be some namespaced value using dotted
       notation to avoid duplicates, e.g.
       ``"pretix.plugins.banktransfer.incoming_transfer"``.
    :type action_type: str
    :param data: Arbitrary data that can be used by the log action renderer
    :type data: str
    """
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(db_index=True)
    content_object = GenericForeignKey('content_type', 'object_id')
    datetime = models.DateTimeField(auto_now_add=True, db_index=True)
    user = models.ForeignKey('User', null=True, blank=True, on_delete=models.PROTECT)
    api_token = models.ForeignKey('TeamAPIToken', null=True, blank=True, on_delete=models.PROTECT)
    device = models.ForeignKey('Device', null=True, blank=True, on_delete=models.PROTECT)
    oauth_application = models.ForeignKey('pretixapi.OAuthApplication', null=True, blank=True, on_delete=models.PROTECT)
    event = models.ForeignKey('Event', null=True, blank=True, on_delete=models.SET_NULL)
    action_type = models.CharField(max_length=255)
    data = models.TextField(default='{}')
    visible = models.BooleanField(default=True)
    shredded = models.BooleanField(default=False)

    objects = VisibleOnlyManager()
    all = models.Manager()

    class Meta:
        ordering = ('-datetime', '-id')

    def display(self):
        from ..signals import logentry_display

        for receiver, response in logentry_display.send(self.event, logentry=self):
            if response:
                return response
        return self.action_type

    @cached_property
    def organizer(self):
        if self.event:
            return self.event.organizer
        elif hasattr(self.content_object, 'event'):
            return self.content_object.event.organizer
        elif hasattr(self.content_object, 'organizer'):
            return self.content_object.organizer
        return None

    @cached_property
    def display_object(self):
        from . import (
            Event, Item, ItemCategory, Order, Question, Quota, SubEvent,
            TaxRule, Voucher,
        )

        try:
            if self.content_type.model_class() is Event:
                return ''

            co = self.content_object
        except:
            return ''
        a_map = None
        a_text = None

        if isinstance(co, Order):
            a_text = _('Order {val}')
            a_map = {
                'href': reverse('control:event.order', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'code': co.code
                }),
                'val': escape(co.code),
            }
        elif isinstance(co, Voucher):
            a_text = _('Voucher {val}…')
            a_map = {
                'href': reverse('control:event.voucher', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'voucher': co.id
                }),
                'val': escape(co.code[:6]),
            }
        elif isinstance(co, Item):
            a_text = _('Product {val}')
            a_map = {
                'href': reverse('control:event.item', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'item': co.id
                }),
                'val': escape(co.name),
            }
        elif isinstance(co, SubEvent):
            a_text = pgettext_lazy('subevent', 'Date {val}')
            a_map = {
                'href': reverse('control:event.subevent', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'subevent': co.id
                }),
                'val': escape(str(co))
            }
        elif isinstance(co, Quota):
            a_text = _('Quota {val}')
            a_map = {
                'href': reverse('control:event.items.quotas.show', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'quota': co.id
                }),
                'val': escape(co.name),
            }
        elif isinstance(co, ItemCategory):
            a_text = _('Category {val}')
            a_map = {
                'href': reverse('control:event.items.categories.edit', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'category': co.id
                }),
                'val': escape(co.name),
            }
        elif isinstance(co, Question):
            a_text = _('Question {val}')
            a_map = {
                'href': reverse('control:event.items.questions.show', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'question': co.id
                }),
                'val': escape(co.question),
            }
        elif isinstance(co, TaxRule):
            a_text = _('Tax rule {val}')
            a_map = {
                'href': reverse('control:event.settings.tax.edit', kwargs={
                    'event': self.event.slug,
                    'organizer': self.event.organizer.slug,
                    'rule': co.id
                }),
                'val': escape(co.name),
            }

        if a_text and a_map:
            a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
            return a_text.format_map(a_map)
        elif a_text:
            return a_text
        else:
            for receiver, response in logentry_object_link.send(self.event, logentry=self):
                if response:
                    return response
            return ''

    @cached_property
    def parsed_data(self):
        return json.loads(self.data)

    def delete(self, using=None, keep_parents=False):
        raise TypeError("Logs cannot be deleted.")
class Like(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
Example #21
0
class CharFieldGFK(models.Model):
    name = models.CharField(max_length=255)
    object_id = models.TextField()
    content_type = models.ForeignKey(ContentType)
    content_object = GenericForeignKey(ct_field='content_type',
                                       fk_field='object_id')
def object_relation_mixin_factory(prefix=None,
                                  prefix_verbose=None,
                                  add_related_name=False,
                                  limit_content_type_choices_to=None,
                                  limit_object_choices_to=None,
                                  is_required=False):
    """
    Returns a mixin class for generic foreign keys using
    "Content type - object ID" with dynamic field names.
    This function is just a class generator.

    Parameters:
    prefix:           a prefix, which is added in front of
                      the fields
    prefix_verbose:   a verbose name of the prefix, used to
                      generate a title for the field column
                      of the content object in the Admin
    add_related_name: a boolean value indicating, that a
                      related name for the generated content
                      type foreign key should be added. This
                      value should be true, if you use more
                      than one ObjectRelationMixin in your
                      model.

    The model fields are created using this naming scheme:
        <<prefix>>_content_type
        <<prefix>>_object_id
        <<prefix>>_content_object
    """
    p = ""
    if prefix:
        p = f"{prefix}_"

    prefix_verbose = prefix_verbose or _("Related object")
    limit_content_type_choices_to = limit_content_type_choices_to or {}
    limit_object_choices_to = limit_object_choices_to or {}

    content_type_field = f"{p}content_type"
    object_id_field = f"{p}object_id"
    content_object_field = f"{p}content_object"

    class TheClass(models.Model):
        class Meta:
            abstract = True

    if add_related_name:
        if not prefix:
            raise FieldError("if add_related_name is set to "
                             "True, a prefix must be given")
        related_name = prefix
    else:
        related_name = None

    optional = not is_required

    ct_verbose_name = _(f"{prefix_verbose}'s type (model)")

    content_type = models.ForeignKey(
        ContentType,
        verbose_name=ct_verbose_name,
        related_name=related_name,
        blank=optional,
        null=optional,
        help_text=_("Please select the type (model) "
                    "for the relation, you want to build."),
        limit_choices_to=limit_content_type_choices_to,
        on_delete=models.CASCADE)

    fk_verbose_name = prefix_verbose

    object_id = models.CharField(
        fk_verbose_name,
        blank=optional,
        null=False,
        help_text=_("Please enter the ID of the related object."),
        max_length=255,
        default="")  # for migrations
    object_id.limit_choices_to = limit_object_choices_to
    # can be retrieved by
    # MyModel._meta.get_field("object_id").limit_choices_to

    content_object = GenericForeignKey(ct_field=content_type_field,
                                       fk_field=object_id_field)

    TheClass.add_to_class(content_type_field, content_type)
    TheClass.add_to_class(object_id_field, object_id)
    TheClass.add_to_class(content_object_field, content_object)

    return TheClass
Example #23
0
class Task(models.Model):
    id = models.BigAutoField(primary_key=True, editable=False)
    _workflow = models.ForeignKey(
        Workflow,
        on_delete=models.CASCADE,
        db_column="workflow_id",
        editable=False,
    )
    content_type = models.ForeignKey(
        "contenttypes.ContentType",
        on_delete=models.CASCADE,
        editable=False,
        limit_choices_to=workflow_state_subclasses,
        related_name="joeflow_task_set",
    )
    workflow = GenericForeignKey("content_type",
                                 "_workflow_id",
                                 for_concrete_model=False)

    name = models.CharField(max_length=255, db_index=True, editable=False)

    HUMAN = "human"
    MACHINE = "machine"
    _type_choices = (
        (HUMAN, t(HUMAN)),
        (MACHINE, t(MACHINE)),
    )
    type = models.CharField(
        max_length=50,
        choices=_type_choices,
        editable=False,
        db_index=True,
    )

    parent_task_set = models.ManyToManyField(
        "self",
        related_name="child_task_set",
        editable=False,
        symmetrical=False,
    )

    FAILED = "failed"
    SUCCEEDED = "succeeded"
    SCHEDULED = "scheduled"
    CANCELED = "canceled"
    _status_choices = (
        (FAILED, t(FAILED)),
        (SUCCEEDED, t(SUCCEEDED)),
        (SCHEDULED, t(SCHEDULED)),
        (CANCELED, t(CANCELED)),
    )
    status = models.CharField(
        max_length=50,
        choices=_status_choices,
        default=SCHEDULED,
        editable=False,
        db_index=True,
    )

    assignees = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        verbose_name=t("assignees"),
        related_name="joeflow_assignee_task_set",
    )

    completed_by_user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name=t("completed by"),
        related_name="joeflow_completed_by_task_set",
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

    created = models.DateTimeField(auto_now_add=True, db_index=True)
    modified = models.DateTimeField(auto_now=True, db_index=True)
    completed = models.DateTimeField(blank=True,
                                     null=True,
                                     editable=False,
                                     db_index=True)

    exception = models.TextField(blank=True)
    stacktrace = models.TextField(blank=True)

    objects = TasksQuerySet.as_manager()

    class Meta:
        ordering = ("-completed", "-created")
        get_latest_by = ("created", )
        permissions = (
            ("rerun", t("Can rerun failed tasks.")),
            ("cancel", t("Can cancel failed tasks.")),
        )
        default_manager_name = "objects"

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

    def save(self, **kwargs):
        if self.pk:
            try:
                update_fields = kwargs["update_fields"]
            except KeyError as e:
                raise ValueError(
                    "You need to provide explicit 'update_fields' to avoid race conditions."
                ) from e
            else:
                update_fields.append("modified")
        super().save(**kwargs)

    def get_absolute_url(self):
        if self.completed:
            return
        url_name = "{}:{}".format(self.workflow.get_url_namespace(), self.name)
        try:
            return reverse(url_name, kwargs=dict(pk=self.pk))
        except NoReverseMatch:
            pass

    @property
    def node(self):
        return getattr(type(self.workflow), self.name)

    def finish(self, user=None):
        self.completed = timezone.now()
        self.status = self.SUCCEEDED
        if user and not user.is_authenticated:
            user = None
        self.completed_by_user = user
        if self.pk:
            self.save(
                update_fields=["status", "completed", "completed_by_user"])
        else:
            self.save()

    def cancel(self, user=None):
        self.completed = timezone.now()
        self.status = self.CANCELED
        if user and not user.is_authenticated:
            user = None
        self.completed_by_user = user
        self.save(update_fields=["status", "completed", "completed_by_user"])

    def fail(self):
        self.completed = timezone.now()
        self.status = self.FAILED
        tb = traceback.format_exception(*sys.exc_info())
        self.exception = tb[-1].strip()
        self.stacktrace = "".join(tb)
        self.save(update_fields=["status", "exception", "stacktrace"])

    def enqueue(self, countdown=None, eta=None):
        """
        Schedule the tasks for execution.

        Args:
            countdown (int):
                Time in seconds until the time should be started.

            eta (datetime.datetime):
                Time at which the task should be started.

        Returns:
            celery.result.AsyncResult: Celery task result.

        """
        self.status = self.SCHEDULED
        self.completed = None
        self.exception = ""
        self.stacktrace = ""
        self.save(
            update_fields=["status", "completed", "exception", "stacktrace"])
        task_runner = import_string(settings.JOEFLOW_TASK_RUNNER)
        transaction.on_commit(lambda: task_runner(
            task_pk=self.pk,
            workflow_pk=self._workflow_id,
            countdown=countdown,
            eta=eta,
        ))

    def start_next_tasks(self, next_nodes: list = None):
        """
        Start new tasks following another tasks.

        Args:
            self (Task): The task that precedes the next tasks.
            next_nodes (list):
                List of nodes that should be executed next. This argument is
                optional. If no nodes are provided it will default to all
                possible edges.

        """
        if next_nodes is None:
            next_nodes = self.workflow.get_next_nodes(self.node)
        tasks = []
        for node in next_nodes:
            try:
                # Some nodes – like Join – implement their own method to create new tasks.
                task = node.create_task(self.workflow)
            except AttributeError:
                task = self.workflow.task_set.create(name=node.name,
                                                     type=node.type,
                                                     workflow=self.workflow)
            task.parent_task_set.add(self)
            if callable(node):
                transaction.on_commit(task.enqueue)
            tasks.append(task)
        return tasks
Example #24
0
class AccessControlList(models.Model):
    """
    ACL means Access Control List it is a more fine-grained method of
    granting access to objects. In the case of ACLs, they grant access using
    3 elements: actor, permission, object. In this case the actor is the role,
    the permission is the Mayan permission and the object can be anything:
    a document, a folder, an index, etc. This means = "Grant X permissions
    to role Y for object Z". This model holds the permission, object, actor
    relationship for one access control list.
    Fields:
    * Role - Custom role that is being granted a permission. Roles are created
    in the Setup menu.
    """
    content_type = models.ForeignKey(on_delete=models.CASCADE,
                                     related_name='object_content_type',
                                     to=ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey(
        ct_field='content_type',
        fk_field='object_id',
    )
    permissions = models.ManyToManyField(blank=True,
                                         related_name='acls',
                                         to=StoredPermission,
                                         verbose_name=_('Permissions'))
    role = models.ForeignKey(on_delete=models.CASCADE,
                             related_name='acls',
                             to=Role,
                             verbose_name=_('Role'))

    objects = AccessControlListManager()

    class Meta:
        ordering = ('pk', )
        unique_together = ('content_type', 'object_id', 'role')
        verbose_name = _('Access entry')
        verbose_name_plural = _('Access entries')

    def __str__(self):
        return _('Role "%(role)s" permission\'s for "%(object)s"') % {
            'object': self.content_object,
            'role': self.role,
        }

    def get_absolute_url(self):
        return reverse(viewname='acls:acl_permissions', kwargs={'pk': self.pk})

    def get_inherited_permissions(self):
        return AccessControlList.objects.get_inherited_permissions(
            obj=self.content_object, role=self.role)

    def permissions_add(self, queryset, _user=None):
        with transaction.atomic():
            event_acl_edited.commit(actor=_user, target=self)
            self.permissions.add(*queryset)

    def permissions_remove(self, queryset, _user=None):
        with transaction.atomic():
            event_acl_edited.commit(actor=_user, target=self)
            self.permissions.remove(*queryset)

    def save(self, *args, **kwargs):
        _user = kwargs.pop('_user', None)

        with transaction.atomic():
            is_new = not self.pk
            super(AccessControlList, self).save(*args, **kwargs)
            if is_new:
                event_acl_created.commit(actor=_user, target=self)
            else:
                event_acl_edited.commit(actor=_user, target=self)
Example #25
0
class Notification(models.Model):

    """
    **Notification Model for storing notifications. (Yeah, too obvious)**

    This model is pretty-much a replica of ``django-notifications``'s
    model. The newly added fields just adds a feature to allow anonymous
    ``actors``, ``targets`` and ``object``.

    **Attributes**:
        :recipient:     The user who receives notification.
        :verb:          Action performed by actor (not necessarily).
        :description:   Option description for your notification.

        :actor_text:    Anonymous actor who is not in content-type.
        :actor_url:     Since the actor is not in content-type,
                        a custom URL for it.

        *...Same for target and obj*.

        :nf_type:   | Each notification is different, they must be formatted
                    | differently during HTML rendering. For this, each
                    | notification gets to carry it own *notification type*.
                    |
                    | This notification type will be used to search
                    | the special template for the notification located at
                    | ``notifications/includes/NF_TYPE.html`` of your
                    | template directory.
                    |
                    | The main reason to add this field is to save you
                    | from the pain of writing ``if...elif...else`` blocks
                    | in your template file just for handling how
                    | notifications will get rendered.
                    |
                    | With this, you can just save template for an individual
                    | notification type and call the *template-tag* to render
                    | all notifications for you without writing a single
                    | ``if...elif...else block``.
                    |
                    | You'll just need to do a
                    | ``{% render_notifications using NOTIFICATION_OBJ %}``
                    | and you'll get your notifications rendered.
                    |
                    | By default, every ``nf_type`` is set to ``default``.

    :extra:     **JSONField**, holds other optional data you want the
                notification to carry in JSON format.

    :deleted:   Useful when you want to *soft delete* your notifications.

    """

    recipient = models.ForeignKey(settings.AUTH_USER_MODEL,
                                  related_name='notifications',
                                  on_delete=models.CASCADE,
                                  verbose_name=_('Notification receiver'))

    # actor attributes.
    actor_content_type = models.ForeignKey(
        ContentType, null=True, blank=True,
        related_name='notify_actor', on_delete=models.CASCADE,
        verbose_name=_('Content type of actor object'))

    actor_object_id = models.PositiveIntegerField(
        null=True, blank=True,
        verbose_name=_('ID of the actor object'))

    actor_content_object = GenericForeignKey('actor_content_type',
                                             'actor_object_id')

    actor_text = models.CharField(
        max_length=50, blank=True, null=True,
        verbose_name=_('Anonymous text for actor'))

    actor_url_text = models.CharField(
        blank=True, null=True, max_length=200,
        verbose_name=_('Anonymous URL for actor'))

    # basic details.
    verb = models.CharField(max_length=50,
                            verbose_name=_('Verb of the action'))

    description = models.CharField(
        max_length=255, blank=True, null=True,
        verbose_name=_('Description of the notification'))

    nf_type = models.CharField(max_length=20, default='default',
                               verbose_name=_('Type of notification'))

    # TODO: Add a field to store notification cover images.

    # target attributes.
    target_content_type = models.ForeignKey(
        ContentType, null=True, blank=True,
        related_name='notify_target', on_delete=models.CASCADE,
        verbose_name=_('Content type of target object'))

    target_object_id = models.PositiveIntegerField(
        null=True, blank=True,
        verbose_name=_('ID of the target object'))

    target_content_object = GenericForeignKey('target_content_type',
                                              'target_object_id')

    target_text = models.CharField(
        max_length=50, blank=True, null=True,
        verbose_name=_('Anonymous text for target'))

    target_url_text = models.CharField(
        blank=True, null=True, max_length=200,
        verbose_name=_('Anonymous URL for target'))

    # obj attributes.
    obj_content_type = models.ForeignKey(
        ContentType, null=True, blank=True,
        related_name='notify_object', on_delete=models.CASCADE,
        verbose_name=_('Content type of action object'))

    obj_object_id = models.PositiveIntegerField(
        null=True, blank=True,
        verbose_name=_('ID of the target object'))

    obj_content_object = GenericForeignKey('obj_content_type', 'obj_object_id')

    obj_text = models.CharField(
        max_length=50, blank=True, null=True,
        verbose_name=_('Anonymous text for action object'))

    obj_url_text = models.CharField(
        blank=True, null=True, max_length=200,
        verbose_name=_('Anonymous URL for action object'))

    extra = JSONField(null=True, blank=True,
                      verbose_name=_('JSONField to store addtional data'))

    # Advanced details.
    created = models.DateTimeField(auto_now=False, auto_now_add=True)
    read = models.BooleanField(default=False,
                               verbose_name=_('Read status'))
    deleted = models.BooleanField(default=False,
                                  verbose_name=_('Soft delete status'))

    objects = NotificationQueryset.as_manager()

    class Meta(object):
        ordering = ('-created', )

    def __str__(self):
        ctx = {
            'actor': self.actor or self.actor_text,
            'verb': self.verb,
            'description': self.description,
            'target': self.target or self.target_text,
            'obj': self.obj or self.obj_text,
            'at': timesince(self.created),
        }

        if ctx['actor']:
            if not ctx['target']:
                return _("{actor} {verb} {at} ago").format(**ctx)
            elif not ctx['obj']:
                return _("{actor} {verb} on {target} {at} ago").format(**ctx)
            elif ctx['obj']:
                return _(
                    "{actor} {verb} {obj} on {target} {at} ago").format(**ctx)
        return _("{description} -- {at} ago").format(**ctx)

    def mark_as_read(self):
        """
        Marks notification as read
        """
        self.read = True
        self.save()

    def mark_as_unread(self):
        """
        Marks notification as unread.
        """
        self.read = False
        self.save()

    @cached_property
    def actor(self):
        """
        Property to return actor object/text to keep things DRY.

        :return: Actor object or Text or None.
        """
        return self.actor_content_object or self.actor_text

    @cached_property
    def actor_url(self):
        """
        Property to return permalink of the actor.
        Uses ``get_absolute_url()``.

        If ``get_absolute_url()`` method fails, it tries to grab URL
        from ``actor_url_text``, if it fails again, returns a "#".

        :return: URL for the actor.
        """
        try:
            url = self.actor_content_object.get_absolute_url()
        except AttributeError:
            url = self.actor_url_text or "#"
        return url

    @cached_property
    def target(self):
        """
        See ``actor`` property

        :return: Target object or Text or None
        """
        return self.target_content_object or self.target_text

    @cached_property
    def target_url(self):
        """
        See ``actor_url`` property.

        :return: URL for the target.
        """
        try:
            url = self.target_content_object.get_absolute_url()
        except AttributeError:
            url = self.target_url_text or "#"
        return url

    @cached_property
    def obj(self):
        """
        See ``actor`` property.

        :return: Action Object or Text or None.
        """
        return self.obj_content_object or self.obj_text

    @cached_property
    def obj_url(self):
        """
        See ``actor_url`` property.

        :return: URL for Action Object.
        """
        try:
            url = self.obj_content_object.get_absolute_url()
        except AttributeError:
            url = self.obj_url_text or "#"
        return url

    @staticmethod
    def do_escape(obj):
        """
        Method to HTML escape an object or set it to None conditionally.
        performs ``force_text()`` on the argument so that a foreignkey gets
        serialized? and spit out the ``__str__`` output instead of an Object.

        :param obj: Object to escape.

        :return: HTML escaped and JSON-friendly data.
        """
        return escape(force_text(obj)) if obj else None

    def as_json(self):
        """
        Notification data in a Python dictionary to which later gets
        supplied to JSONResponse so that it gets JSON serialized
        the *django-way*

        :return: Dictionary format of the QuerySet object.
        """
        data = {
            "id": self.id,
            "actor": self.do_escape(self.actor),
            "actor_url": self.do_escape(self.actor_url),
            "verb": self.do_escape(self.verb),
            "description": self.do_escape(self.description),
            "read": self.read,
            "nf_type": self.do_escape(self.nf_type),
            "target": self.do_escape(self.target),
            "target_url": self.do_escape(self.target_url),
            "obj": self.do_escape(self.obj),
            "obj_url": self.do_escape(self.obj_url),
            "created": self.created,
            "data": self.extra,
        }
        return data
Example #26
0
class ForConcreteModelModel(models.Model):
    content_type = models.ForeignKey(ContentType, models.CASCADE)
    object_id = models.PositiveIntegerField()
    obj = GenericForeignKey()
Example #27
0
class LikeCount(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    liked_num = models.IntegerField(default=0)
Example #28
0
class AllowsNullGFK(models.Model):
    content_type = models.ForeignKey(ContentType, models.SET_NULL, null=True)
    object_id = models.PositiveIntegerField(null=True)
    content_object = GenericForeignKey()
Example #29
0
class ForProxyModelModel(models.Model):
    content_type = models.ForeignKey(ContentType, models.CASCADE)
    object_id = models.PositiveIntegerField()
    obj = GenericForeignKey(for_concrete_model=False)
    title = models.CharField(max_length=255, null=True)
Example #30
0
class ReadStatistics(models.Model):
    read_num = models.IntegerField(default=0)

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
Example #31
0
 def __init__(self, ct_field="content_type", fk_field="object_id",
              verbose_name=None, help_text=None, dont_merge=False):
     self.verbose_name = verbose_name
     self.help_text = help_text
     self.dont_merge = dont_merge
     DjangoGenericForeignKey.__init__(self, ct_field, fk_field)
Example #32
0
class TaggedTool(models.Model):
    tool = models.ForeignKey(Tool)
    content_type = models.ForeignKey("contenttypes.ContentType")
    object_id = models.IntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    order = models.IntegerField(default=9999)