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)
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 }
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)
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')
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')
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)
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, )
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)
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')
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')
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__()
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]
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)
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, )
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()
class Award(models.Model): name = models.CharField(max_length=25) object_id = models.PositiveIntegerField() content_type = models.ForeignKey(ContentType, models.CASCADE) content_object = GenericForeignKey()
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
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')
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')
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
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
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)
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
class ForConcreteModelModel(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) object_id = models.PositiveIntegerField() obj = GenericForeignKey()
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)
class AllowsNullGFK(models.Model): content_type = models.ForeignKey(ContentType, models.SET_NULL, null=True) object_id = models.PositiveIntegerField(null=True) content_object = GenericForeignKey()
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)
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')
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)
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)