class PolicyHolder(core_models.HistoryBusinessModel):
    code = models.CharField(db_column='PolicyHolderCode', max_length=32)
    trade_name = models.CharField(db_column='TradeName', max_length=255)
    locations = models.ForeignKey(Location,
                                  db_column='LocationsId',
                                  on_delete=models.deletion.DO_NOTHING,
                                  blank=True,
                                  null=True)
    address = FallbackJSONField(db_column='Address', blank=True, null=True)
    phone = models.CharField(db_column='Phone',
                             max_length=16,
                             blank=True,
                             null=True)
    fax = models.CharField(db_column='Fax',
                           max_length=16,
                           blank=True,
                           null=True)
    email = models.CharField(db_column='Email',
                             max_length=255,
                             blank=True,
                             null=True)
    contact_name = FallbackJSONField(db_column='ContactName',
                                     blank=True,
                                     null=True)
    legal_form = models.IntegerField(db_column='LegalForm',
                                     blank=True,
                                     null=True)
    activity_code = models.IntegerField(db_column='ActivityCode',
                                        blank=True,
                                        null=True)
    accountancy_account = models.CharField(db_column='AccountancyAccount',
                                           max_length=64,
                                           blank=True,
                                           null=True)
    bank_account = FallbackJSONField(db_column="bankAccount",
                                     blank=True,
                                     null=True)
    payment_reference = models.CharField(db_column='PaymentReference',
                                         max_length=128,
                                         blank=True,
                                         null=True)

    objects = PolicyHolderManager()

    @classmethod
    def get_queryset(cls, queryset, user):
        queryset = cls.filter_queryset(queryset)
        if isinstance(user, ResolveInfo):
            user = user.context.user
        if settings.ROW_SECURITY and user.is_anonymous:
            return queryset.filter(id=None)
        if settings.ROW_SECURITY:
            dist = UserDistrict.get_user_districts(user._u)
            return queryset.filter(
                locations__parent__parent_id__in=[l.location_id for l in dist])
        return queryset

    class Meta:
        db_table = 'tblPolicyHolder'
Example #2
0
class WorkerHeartbeat(models.Model):
    """
    A worker heartbeat event.

    Stores time varying properties of the worker as available from
    Celery worker monitoring events. The names of the fields of this model
    are intended to be consistent with those.
    See https://docs.celeryproject.org/en/stable/userguide/monitoring.html#worker-events
    """

    worker = models.ForeignKey(
        Worker,
        on_delete=models.CASCADE,
        related_name="heartbeats",
        help_text="The worker that the heartbeat is for.",
    )

    time = models.DateTimeField(help_text="The time of the heartbeat.")

    clock = models.IntegerField(
        help_text="The tick number of the worker's monotonic clock", )

    active = models.IntegerField(
        help_text="The number of active jobs on the worker.", )

    processed = models.IntegerField(
        help_text="The number of jobs that have been processed by the worker.",
    )

    load = FallbackJSONField(
        help_text=
        "An array of the system load over the last 1, 5 and 15 minutes. From os.getloadavg().",
    )
Example #3
0
class Book(models.Model):
    data = FallbackJSONField(encoder=DjangoJSONEncoder,
                             null=False,
                             default={'foo': 'bar'})

    def __str__(self):
        return str(self.data['title'])
class ContractDetails(core_models.HistoryModel):
    contract = models.ForeignKey(Contract,
                                 db_column="ContractUUID",
                                 on_delete=models.deletion.CASCADE)
    insuree = models.ForeignKey(Insuree,
                                db_column='InsureeID',
                                on_delete=models.deletion.DO_NOTHING)
    contribution_plan_bundle = models.ForeignKey(
        ContributionPlanBundle,
        db_column='ContributionPlanBundleUUID',
        on_delete=models.deletion.DO_NOTHING)

    json_param = FallbackJSONField(db_column='Json_param',
                                   blank=True,
                                   null=True)

    objects = ContractDetailsManager()

    @classmethod
    def get_queryset(cls, queryset, user):
        queryset = cls.filter_queryset(queryset)
        if isinstance(user, ResolveInfo):
            user = user.context.user
        if settings.ROW_SECURITY and user.is_anonymous:
            return queryset.filter(id=-1)
        if settings.ROW_SECURITY:
            pass
        return queryset

    class Meta:
        db_table = 'tblContractDetails'
Example #5
0
class Log(models.Model):
    event = models.ForeignKey(to=Event,
                              on_delete=models.CASCADE,
                              related_name='logs')
    state = models.CharField(
        max_length=11,
        choices=(
            ("new", "new"),
            ("ok", "ok"),
            ("unreachable", "unreachable"),
            ("error", "error"),
        ),
        verbose_name=_("State"),
    )
    timestamp = models.DateTimeField(auto_now_add=True)
    content = FallbackJSONField(null=True)
Example #6
0
class Attendee(LoggedModel):
    event = models.ForeignKey('pretixbase.Event',
                              on_delete=models.CASCADE,
                              related_name="attendees")
    verified = models.BooleanField(default=False)
    email = models.EmailField(unique=True,
                              db_index=True,
                              null=False,
                              blank=False,
                              verbose_name=_('E-mail'),
                              max_length=190)
    profile = FallbackJSONField(blank=True, default=dict)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['created_at']

    def has_profile(self):
        return self.profile
Example #7
0
class Pipeline(models.Model):
    """
    A pipeline is a template for one or more jobs.

    Pipelines provide an interface for users to define jobs
    which can then be run on demand, in response to triggers
    (e.g. a push to a repository), or on a schedule.

    Pipelines are not the only way that jobs are created.
    For example, a `convert` job is implicitly created when a user
    uses the "Save as" command.

    Pipelines are superficially similar to continuous integration tools
    like Travis, Github Actions, Azure Pipelines etc but differ
    in that they are focussed on document data flows, including pulling
    and pushing documents from/to alternative sources.

    A pipeline's definition is stored as JSON but can be authored
    by users using YAML or a GUI.
    """

    project = models.ForeignKey(
        "projects.Project",
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="pipelines",
        help_text="The project this pipeline is linked to.",
    )

    name = models.SlugField(
        max_length=256,
        help_text="A name for this pipeline. Must be unique to the project.",
    )

    definition = FallbackJSONField(
        help_text="The JSON definition of the pipeline.")

    schedule = models.OneToOneField(
        "PipelineSchedule",
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="pipeline",
        help_text="The schedule for this pipeline.",
    )

    def save(self, *args, **kwargs):
        """
        Override save to set necessary fields for the schedule.

        The `pipeline` task must be defined in workers and take
        a `definition` argument that will be transformed into a
        jobs and run on the worker.
        """
        if self.schedule:
            self.schedule.name = "Pipeline {} schedule".format(self.name)
            self.schedule.task = "pipeline"
            self.schedule.kwargs = json.dumps(dict(definition=self.definition))
            # TODO Choose a queue based on the project
            self.schedule.queue = "stencila"
            self.schedule.save()
        return super().save(*args, **kwargs)
Example #8
0
class Job(models.Model):
    """
    A job, usually, but not necessarily associated with a project.

    If a job is created here in Django, the `creator` field should be
    populated with the current user. Jobs created as part of a pipline
    may not have a creator.

    The fields `method`, `params`, `result` correspond to
    the same properties in [JSON RPC](https://www.jsonrpc.org/specification).

    The `log` field is an array of JSON objects, each corresponding to
    a log entry. Each entry is exected to have a structure of a
    [Logga](https://github.com/stencila/logga) log entry. It will include
    any errors while running the job.

    The fields `queue`, `worker` and `retries` provide for additional
    information on where and how the job was executed.
    """

    project = models.ForeignKey(
        "projects.Project",
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="jobs",
        help_text="The project this job is associated with.",
    )

    creator = models.ForeignKey(
        User,
        null=True,  # Should only be null if the creator is deleted
        blank=True,
        on_delete=models.SET_NULL,
        related_name="jobs_created",
        help_text="The user who created the job.",
    )

    created = models.DateTimeField(null=True,
                                   auto_now_add=True,
                                   help_text="The time the job was created.")

    queue = models.ForeignKey(
        Queue,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="jobs",
        help_text="The queue that this job was routed to",
    )

    parent = models.ForeignKey(
        "Job",
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="children",
        help_text="The parent job",
    )

    began = models.DateTimeField(null=True,
                                 help_text="The time the job began.")
    ended = models.DateTimeField(null=True,
                                 help_text="The time the job ended.")
    status = models.CharField(
        max_length=32,
        choices=JobStatus.as_choices(),
        blank=True,
        null=True,
        help_text="The current status of the job.",
    )

    method = models.CharField(max_length=32,
                              choices=JobMethod.as_choices(),
                              help_text="The job method.")
    params = FallbackJSONField(
        blank=True,
        null=True,
        help_text="The parameters of the job; a JSON object.")
    result = FallbackJSONField(
        blank=True,
        null=True,
        help_text="The result of the job; a JSON value.")
    error = FallbackJSONField(
        blank=True,
        null=True,
        help_text=
        "Any error associated with the job; a JSON object with type, message etc.",
    )

    log = FallbackJSONField(
        blank=True,
        null=True,
        help_text=
        "The job log; a JSON array of log objects, including any errors.",
    )
    runtime = models.FloatField(blank=True,
                                null=True,
                                help_text="The running time of the job.")

    # Not a URLField because potential for non-standard schemes e.g. ws://
    url = models.CharField(
        max_length=256,
        blank=True,
        null=True,
        help_text=
        "The URL of the job on the local network; can be used to interact with it.",
    )

    users = models.ManyToManyField(
        User,
        help_text=
        "The users who have connected to the job; not necessarily currently connected.",
    )

    worker = models.CharField(
        max_length=64,
        blank=True,
        null=True,
        help_text="The identifier of the worker that ran the job.",
    )
    retries = models.IntegerField(
        blank=True,
        null=True,
        help_text="The number of retries to fulfil the job.",
    )

    @property
    def position(self):
        """
        Position of the job in the queue.

        Counts the number of jobs that were `created` before this
        job and that have no `ended` date.
        """
        return (Job.objects.filter(created__lt=self.created,
                                   ended__isnull=True).count() + 1)

    @property
    def get_runtime(self):
        """
        Format the runtime into a format that can be printed to the screen.

        i.e. convert from float into hours:mins format.
        It follows the following rules:
        - If the job has not started, return empty string
        - If job has started & not ended calculate time relative to now.
        - If job has ended, calculate difference.
        """
        if self.began is not None:
            now = datetime.now(timezone.utc)
            difference = now - self.began
            p = inflect.engine()

            if self.ended is not None:
                difference = self.ended - self.began

            h, rem = divmod(difference.seconds, 3600)
            m, s = divmod(rem, 60)

            output = [
                "%d %s" % (h, p.plural("hour", h)) if h != 0 else "",
                "%d %s" % (m, p.plural("min", m)) if m != 0 else "",
                "%d %s" % (s, p.plural("sec", s)) if s != 0 else "",
            ]

            return " ".join(x for x in output)

        return ""

    @property
    def icon(self):
        """Get the icon from the status - used in the template."""
        return JobStatus.icon(self.status)

    @property
    def colour(self):
        """Get the colour from the status - used in the template."""
        return "is-{}".format(JobStatus.colour(self.status))

    @property
    def status_label(self):
        """Get a printable version of the status - used in the template."""
        if self.status is None:
            # TODO: If there is no status, assume it's dispatched?
            # self.status = JobStatus.DISPATCHED.name
            return None

        status = JobStatus[self.status]

        return status.value

    @property
    def has_ended(self):
        """Check if the status is one of the defined ended statuses."""
        return JobStatus.has_ended(self.status)

    # Shortcuts to the functions for controlling
    # and updating jobs.
    def dispatch(self) -> "Job":
        from jobs.jobs import dispatch_job

        return dispatch_job(self)

    def update(self) -> "Job":
        from jobs.jobs import update_job

        return update_job(self)

    def cancel(self) -> "Job":
        from jobs.jobs import cancel_job

        return cancel_job(self)
Example #9
0
class Worker(models.Model):
    """
    A worker that runs jobs placed on one or more queues.

    This model stores information on a worker as captured by the `overseer` service
    from Celery's worker monitoring events.
    """

    queues = models.ManyToManyField(
        Queue,
        related_name="workers",
        help_text="The queues that this worker is listening to.",
    )

    created = models.DateTimeField(
        auto_now_add=True,
        help_text=
        "The time that the worker started (time of the first event for the worker).",
    )

    started = models.DateTimeField(
        null=True,
        blank=True,
        help_text=
        "The time that the worker started (only recorded on a 'worker-online' event).",
    )

    updated = models.DateTimeField(
        null=True,
        blank=True,
        help_text=
        "The time that the last heatbeat was received for the worker.",
    )

    finished = models.DateTimeField(
        null=True,
        blank=True,
        help_text=
        "The time that the worker finished (only recorded on a 'worker-offline' event)",
    )

    hostname = models.CharField(
        max_length=512,
        help_text="The `hostname` of the worker.",
    )

    utcoffset = models.IntegerField(
        help_text="The `utcoffset` of the worker.", )

    pid = models.IntegerField(help_text="The `pid` of the worker.", )

    freq = models.FloatField(
        help_text="The worker's heatbeat frequency (in seconds)", )

    software = models.CharField(
        max_length=256,
        help_text="The name and version of the worker's software.",
    )

    os = models.CharField(
        max_length=64,
        help_text="Operating system that the worker is running on.",
    )

    details = FallbackJSONField(
        null=True,
        blank=True,
        help_text="Details about the worker including queues and stats"
        "See https://docs.celeryproject.org/en/stable/userguide/workers.html#statistics",
    )

    signature = models.CharField(
        max_length=512,
        help_text="The signature of the worker used to identify it. "
        "It is possible, but unlikely, that two or more active workers have the same signature.",
    )

    # The number of missing heartbeats before a worker is considered
    # as being inactive
    FLATLINE_HEARTBEATS = 5

    @classmethod
    def get_or_create(cls, event: dict, create=False):
        """
        Get or create a worker from a Celery worker event.

        This method extracts the fields of a worker from a
        Celery worker event and constructs a signature from them.
        If there is an active worker with the same signature then
        it is returned.

        The signature is the only way to uniquely identify
        a worker.
        """
        hostname = event.get("hostname")
        utcoffset = event.get("utcoffset")
        pid = event.get("pid")
        freq = event.get("freq")
        software = "{}-{}".format(event.get("sw_ident"), event.get("sw_ver"))
        os = event.get("sw_sys")
        details = event.get("details", {})

        signature = "{hostname}|{utcoffset}|{pid}|{freq}|{software}|{os}".format(
            hostname=hostname,
            utcoffset=utcoffset,
            pid=pid,
            freq=freq,
            software=software,
            os=os,
        )

        if not create:
            try:
                return Worker.objects.get(signature=signature,
                                          finished__isnull=True)
            except Worker.DoesNotExist:
                pass

        return Worker.objects.create(
            hostname=hostname,
            utcoffset=utcoffset,
            pid=pid,
            freq=freq,
            software=software,
            os=os,
            details=details,
            signature=signature,
        )

    @property
    def active(self):
        """Is the worker still active."""
        if self.finished:
            return False
        if self.updated:
            return (timezone.now() - self.updated
                    ).minutes < self.freq * Worker.FLATLINE_HEARTBEATS
        return True
Example #10
0
class CheckinList(LoggedModel):
    event = models.ForeignKey('Event',
                              related_name='checkin_lists',
                              on_delete=models.CASCADE)
    name = models.CharField(max_length=190)
    all_products = models.BooleanField(
        default=True,
        verbose_name=_("All products (including newly created ones)"))
    limit_products = models.ManyToManyField(
        'Item', verbose_name=_("Limit to products"), blank=True)
    subevent = models.ForeignKey('SubEvent',
                                 null=True,
                                 blank=True,
                                 verbose_name=pgettext_lazy(
                                     'subevent', 'Date'),
                                 on_delete=models.CASCADE)
    include_pending = models.BooleanField(
        verbose_name=pgettext_lazy('checkin', 'Include pending orders'),
        default=False,
        help_text=_(
            'With this option, people will be able to check in even if the '
            'order have not been paid.'))
    allow_entry_after_exit = models.BooleanField(
        verbose_name=_('Allow re-entering after an exit scan'), default=True)
    allow_multiple_entries = models.BooleanField(
        verbose_name=_('Allow multiple entries per ticket'),
        help_text=
        _('Use this option to turn off warnings if a ticket is scanned a second time.'
          ),
        default=False)

    auto_checkin_sales_channels = MultiStringField(
        default=[],
        blank=True,
        verbose_name=_('Sales channels to automatically check in'),
        help_text=
        _('All items on this check-in list will be automatically marked as checked-in when purchased through '
          'any of the selected sales channels. This option can be useful when tickets sold at the box office '
          'are not checked again before entry and should be considered validated directly upon purchase.'
          ))
    rules = FallbackJSONField(default=dict, blank=True)

    objects = ScopedManager(organizer='event__organizer')

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

    @property
    def positions(self):
        from . import Order, OrderPosition

        qs = OrderPosition.objects.filter(
            order__event=self.event,
            order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING]
            if self.include_pending else [Order.STATUS_PAID],
        )
        if self.subevent_id:
            qs = qs.filter(subevent_id=self.subevent_id)
        if not self.all_products:
            qs = qs.filter(
                item__in=self.limit_products.values_list('id', flat=True))
        return qs

    @property
    def inside_count(self):
        return self.positions.annotate(
            last_entry=Subquery(
                Checkin.objects.filter(
                    position_id=OuterRef('pk'),
                    list_id=self.pk,
                    type=Checkin.TYPE_ENTRY,
                ).order_by().values('position_id').annotate(
                    m=Max('datetime')).values('m')),
            last_exit=Subquery(
                Checkin.objects.filter(
                    position_id=OuterRef('pk'),
                    list_id=self.pk,
                    type=Checkin.TYPE_EXIT,
                ).order_by().values('position_id').annotate(
                    m=Max('datetime')).values('m')),
        ).filter(
            Q(last_entry__isnull=False)
            & Q(Q(last_exit__isnull=True)
                | Q(last_exit__lt=F('last_entry')))).count()

    @property
    @scopes_disabled()
    # Disable scopes, because this query is safe and the additional organizer filter in the EXISTS() subquery tricks PostgreSQL into a bad
    # subplan that sequentially scans all events
    def checkin_count(self):
        return self.event.cache.get_or_set(
            'checkin_list_{}_checkin_count'.format(self.pk),
            lambda: self.positions.using(settings.DATABASE_REPLICA).annotate(
                checkedin=Exists(
                    Checkin.objects.filter(
                        list_id=self.pk,
                        position=OuterRef('pk'),
                        type=Checkin.TYPE_ENTRY,
                    ))).filter(checkedin=True).count(), 60)

    @property
    def percent(self):
        pc = self.position_count
        return round(self.checkin_count * 100 / pc) if pc else 0

    @property
    def position_count(self):
        return self.event.cache.get_or_set(
            'checkin_list_{}_position_count'.format(self.pk),
            lambda: self.positions.count(), 60)

    def touch(self):
        self.event.cache.delete('checkin_list_{}_position_count'.format(
            self.pk))
        self.event.cache.delete('checkin_list_{}_checkin_count'.format(
            self.pk))

    @staticmethod
    def annotate_with_numbers(qs, event):
        # This is only kept for backwards-compatibility reasons. This method used to precompute .position_count
        # and .checkin_count through a huge subquery chain, but was dropped for performance reasons.
        return qs

    def __str__(self):
        return self.name
Example #11
0
class CheckinList(LoggedModel):
    event = models.ForeignKey('Event', related_name='checkin_lists', on_delete=models.CASCADE)
    name = models.CharField(max_length=190)
    all_products = models.BooleanField(default=True, verbose_name=_("All products (including newly created ones)"))
    limit_products = models.ManyToManyField('Item', verbose_name=_("Limit to products"), blank=True)
    subevent = models.ForeignKey('SubEvent', null=True, blank=True,
                                 verbose_name=pgettext_lazy('subevent', 'Date'), on_delete=models.CASCADE)
    include_pending = models.BooleanField(verbose_name=pgettext_lazy('checkin', 'Include pending orders'),
                                          default=False,
                                          help_text=_('With this option, people will be able to check in even if the '
                                                      'order has not been paid.'))
    gates = models.ManyToManyField(
        'Gate', verbose_name=_("Gates"), blank=True,
        help_text=_("Does not have any effect for the validation of tickets, only for the automatic configuration of "
                    "check-in devices.")
    )
    allow_entry_after_exit = models.BooleanField(
        verbose_name=_('Allow re-entering after an exit scan'),
        default=True
    )
    allow_multiple_entries = models.BooleanField(
        verbose_name=_('Allow multiple entries per ticket'),
        help_text=_('Use this option to turn off warnings if a ticket is scanned a second time.'),
        default=False
    )
    exit_all_at = models.DateTimeField(
        verbose_name=_('Automatically check out everyone at'),
        null=True, blank=True
    )
    auto_checkin_sales_channels = MultiStringField(
        default=[],
        blank=True,
        verbose_name=_('Sales channels to automatically check in'),
        help_text=_('All items on this check-in list will be automatically marked as checked-in when purchased through '
                    'any of the selected sales channels. This option can be useful when tickets sold at the box office '
                    'are not checked again before entry and should be considered validated directly upon purchase.')
    )
    rules = FallbackJSONField(default=dict, blank=True)

    objects = ScopedManager(organizer='event__organizer')

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

    @property
    def positions(self):
        from . import Order, OrderPosition

        qs = OrderPosition.objects.filter(
            order__event=self.event,
            order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.include_pending else [
                Order.STATUS_PAID],
        )
        if self.subevent_id:
            qs = qs.filter(subevent_id=self.subevent_id)
        if not self.all_products:
            qs = qs.filter(item__in=self.limit_products.values_list('id', flat=True))
        return qs

    @property
    def positions_inside(self):
        return self.positions.annotate(
            last_entry=Subquery(
                Checkin.objects.filter(
                    position_id=OuterRef('pk'),
                    list_id=self.pk,
                    type=Checkin.TYPE_ENTRY,
                ).order_by().values('position_id').annotate(
                    m=Max('datetime')
                ).values('m')
            ),
            last_exit=Subquery(
                Checkin.objects.filter(
                    position_id=OuterRef('pk'),
                    list_id=self.pk,
                    type=Checkin.TYPE_EXIT,
                ).order_by().values('position_id').annotate(
                    m=Max('datetime')
                ).values('m')
            ),
        ).filter(
            Q(last_entry__isnull=False)
            & Q(
                Q(last_exit__isnull=True) | Q(last_exit__lt=F('last_entry'))
            )
        )

    @property
    def inside_count(self):
        return self.positions_inside.count()

    @property
    @scopes_disabled()
    # Disable scopes, because this query is safe and the additional organizer filter in the EXISTS() subquery tricks PostgreSQL into a bad
    # subplan that sequentially scans all events
    def checkin_count(self):
        return self.event.cache.get_or_set(
            'checkin_list_{}_checkin_count'.format(self.pk),
            lambda: self.positions.using(settings.DATABASE_REPLICA).annotate(
                checkedin=Exists(Checkin.objects.filter(list_id=self.pk, position=OuterRef('pk'), type=Checkin.TYPE_ENTRY,))
            ).filter(
                checkedin=True
            ).count(),
            60
        )

    @property
    def percent(self):
        pc = self.position_count
        return round(self.checkin_count * 100 / pc) if pc else 0

    @property
    def position_count(self):
        return self.event.cache.get_or_set(
            'checkin_list_{}_position_count'.format(self.pk),
            lambda: self.positions.count(),
            60
        )

    def touch(self):
        self.event.cache.delete('checkin_list_{}_position_count'.format(self.pk))
        self.event.cache.delete('checkin_list_{}_checkin_count'.format(self.pk))

    @staticmethod
    def annotate_with_numbers(qs, event):
        # This is only kept for backwards-compatibility reasons. This method used to precompute .position_count
        # and .checkin_count through a huge subquery chain, but was dropped for performance reasons.
        return qs

    def __str__(self):
        return self.name

    @classmethod
    def validate_rules(cls, rules, seen_nonbool=False, depth=0):
        # While we implement a full jsonlogic machine on Python-level, we also use the logic rules to generate
        # SQL queries, which is not a full implementation of JSON logic right now, but makes some assumptions,
        # e.g. it does not support something like (a AND b) == (c OR D)
        # Every change to our supported JSON logic must be done
        # * in pretix.base.services.checkin
        # * in pretix.base.models.checkin
        # * in checkinrules.js
        # * in libpretixsync
        top_level_operators = {
            '<', '<=', '>', '>=', '==', '!=', 'inList', 'isBefore', 'isAfter', 'or', 'and'
        }
        allowed_operators = top_level_operators | {
            'buildTime', 'objectList', 'lookup', 'var',
        }
        allowed_vars = {
            'product', 'variation', 'now', 'entries_number', 'entries_today', 'entries_days'
        }
        if not rules or not isinstance(rules, dict):
            return

        if len(rules) > 1:
            raise ValidationError(f'Rules should not include dictionaries with more than one key, found: "{rules}".')

        operator = list(rules.keys())[0]

        if operator not in allowed_operators:
            raise ValidationError(f'Logic operator "{operator}" is currently not allowed.')

        if depth == 0 and operator not in top_level_operators:
            raise ValidationError(f'Logic operator "{operator}" is currently not allowed on the first level.')

        values = rules[operator]
        if not isinstance(values, list) and not isinstance(values, tuple):
            values = [values]

        if operator == 'var':
            if values[0] not in allowed_vars:
                raise ValidationError(f'Logic variable "{values[0]}" is currently not allowed.')
            return

        if operator in ('or', 'and') and seen_nonbool:
            raise ValidationError(f'You cannot use OR/AND logic on a level below a comparison operator.')

        for v in values:
            cls.validate_rules(v, seen_nonbool=seen_nonbool or operator not in ('or', 'and'), depth=depth + 1)
Example #12
0
class TicketRequest(LoggedModel):
    STATUS_PENDING = "pending"
    STATUS_APPROVED = "approved"
    STATUS_REJECTED = "rejected"
    STATUS_WITHDRAWN = "withdrawn"
    STATUS_CHOICE = (
        (STATUS_PENDING, _("pending")),
        (STATUS_APPROVED, _("paid")),
        (STATUS_REJECTED, _("expired")),
        (STATUS_WITHDRAWN, _("withdrawn")),
    )

    event = models.ForeignKey('pretixbase.Event',
                              on_delete=models.CASCADE,
                              related_name="ticket_requests")
    voucher = models.ForeignKey('pretixbase.Voucher',
                                verbose_name=_("Assigned voucher"),
                                null=True,
                                blank=True,
                                related_name='ticket_request',
                                on_delete=models.PROTECT)
    name = models.CharField(
        max_length=255,
        verbose_name=_("Full name"),
    )
    email = models.EmailField(unique=True,
                              db_index=True,
                              null=False,
                              blank=False,
                              verbose_name=_('E-mail'),
                              max_length=190)
    status = models.CharField(max_length=10,
                              choices=STATUS_CHOICE,
                              verbose_name=_("Status"),
                              db_index=True,
                              default=STATUS_PENDING)
    data = FallbackJSONField(blank=True, default=dict)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def approved(self):
        return self.status == self.STATUS_APPROVED

    def approve(self, user=None):
        # return if status is not equal to 'pending'
        if self.status != TicketRequest.STATUS_PENDING:
            return False

        # set status to approved if ticket request has a voucher assigned
        if self.voucher:
            self.status = TicketRequest.STATUS_APPROVED
            self.save()
            return False

        self.status = TicketRequest.STATUS_APPROVED
        self.send_voucher(self, user=user)

    def reject(self, user=None):
        if self.status != TicketRequest.STATUS_PENDING:
            return False

        self.status = TicketRequest.STATUS_REJECTED
        self.log_action('pretix.ticket_request.rejected', user=user)
        self.save()

    @property
    def country(self):
        return Country(code=self.data.get('country'))

    def send_voucher(self, quota_cache=None, user=None):
        event = self.event
        quota_id = event.settings.ticket_request_quota
        quota = event.quotas.get(id=quota_id)
        locale = 'en'

        with transaction.atomic():
            v = Voucher.objects.create(
                event=event,
                max_usages=1,
                quota=quota,
                tag='ticket-request',
                valid_until=event.date_to,
                comment=
                _('Automatically created from ticket request entry for {email}'
                  ).format(email=self.email),
                block_quota=True,
            )
            v.log_action('pretix.voucher.approved.ticket_request', {
                'quota': quota,
                'tag': 'ticket-request',
                'block_quota': True,
                'valid_until': v.valid_until.isoformat(),
                'max_usages': 1,
                'email': self.email,
            },
                         user=user)
            self.log_action('pretix.ticket_request.approved', user=user)
            self.voucher = v
            self.save()

        with language(locale):
            email_content = LazyI18nString.from_gettext(
                ugettext_noop(
                    """Congratulations!! You just got approved for an IFF Ticket. You can redeem it in our ticket shop by entering the following voucher code following the directions listed below:

{code}

Alternatively, you can just click on the following link:

<a href="{url}">{url}</a>

If you need a visa for the event, please fill out this form as soon as possible, and someone will get back to you <a href="https://internetfreedomfestival.formstack.com/forms/iff2020_visa">https://internetfreedomfestival.formstack.com/forms/iff2020_visa</a>

We look forward to seeing you soon! If you have any questions, don’t hesitate to reach out to <a href="mailto:[email protected]">[email protected]</a>. We look forward to seeing you at the IFF!

The IFF Team.<br /><br />

<h3>DIRECTIONS TO CLAIM YOUR TICKET</h3>

<ol>
  <li>Visit: <a href="https://tickets.internetfreedomfestival.org/iff/2020/">https://tickets.internetfreedomfestival.org/iff/2020/</a></li>
  <li>Input your voucher code listed above into the section “Redeem a
voucher” and press “REDEEM VOUCHER”</li>
  <li>
    On the next page, select the ticket type you would like by checking
    ONLY ONE of the three checkboxes listed, and then press “PROCEED TO
    CHECKOUT”.<br /><br />

    ---> Tickets to the IFF are free but if you are considering donating,
please select “Supporter Ticket” and include the amount you want to pay,
or select “Organizational Ticket” which has a set rate.<br /><br />

<b>Please Note * If you pick more than one ticket type, your order
may be canceled.</b>
  </li>
  <li>
  Review your order and press “PROCEED WITH CHECKOUT”.<br /><br />

  <b>Please Note * If you select the wrong ticket type, you can restart
the process by clicking the back button on your browser.</b>
  </li>
  <li>
On the next page, please add your email to both boxes listed under
“Contact Information” and press ”CONTINUE”
  </li>
  <li>
An email from “[email protected]” will be sent to you
with a verification code, which you must insert in the following page in
the box labeled “VERIFICATION CODE”
  </li>
  <li>
On the “Your Profile” page, you must fill out your personal
information and press “CONTINUE”
  </li>
  <li>
You will have a chance to review your order once more. If everything
is correct, press “SUBMIT REGISTRATION”
  </li>
  <li>
On the final page, you can download the PDF of your ticket.   You
will need this PDF to enter the IFF. Note, the system will also send you
an email with a PDF, a link to your ticket, as well as your unique
ticket code.
  </li>
</ol>

"""))

            email_context = {
                'event':
                event,
                'url':
                build_absolute_uri(event, 'presale:event.redeem') +
                '?voucher=' + self.voucher.code,
                'code':
                self.voucher.code
            }

            mail(self.email,
                 _('Claim your IFF Ticket!!').format(event=str(event)),
                 email_content,
                 email_context,
                 event,
                 locale=locale)

    class Meta:
        ordering = ['created_at', 'status']
Example #13
0
class WaitingListEntry(LoggedModel):
    event = models.ForeignKey(
        Event,
        on_delete=models.CASCADE,
        related_name="waitinglistentries",
        verbose_name=_("Event"),
    )
    subevent = models.ForeignKey(
        SubEvent,
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        verbose_name=pgettext_lazy("subevent", "Date"),
    )
    created = models.DateTimeField(verbose_name=_("On waiting list since"),
                                   auto_now_add=True)
    name_cached = models.CharField(
        max_length=255,
        verbose_name=_("Name"),
        blank=True,
        null=True,
    )
    name_parts = FallbackJSONField(blank=True, default=dict)
    email = models.EmailField(verbose_name=_("E-mail address"))
    phone = PhoneNumberField(null=True,
                             blank=True,
                             verbose_name=_("Phone number"))
    voucher = models.ForeignKey('Voucher',
                                verbose_name=_("Assigned voucher"),
                                null=True,
                                blank=True,
                                related_name='waitinglistentries',
                                on_delete=models.CASCADE)
    item = models.ForeignKey(Item,
                             related_name='waitinglistentries',
                             on_delete=models.CASCADE,
                             verbose_name=_("Product"),
                             help_text=_("The product the user waits for."))
    variation = models.ForeignKey(
        ItemVariation,
        related_name='waitinglistentries',
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        verbose_name=_("Product variation"),
        help_text=_("The variation of the product selected above."))
    locale = models.CharField(max_length=190, default='en')
    priority = models.IntegerField(default=0)

    objects = ScopedManager(organizer='event__organizer')

    class Meta:
        verbose_name = _("Waiting list entry")
        verbose_name_plural = _("Waiting list entries")
        ordering = ('-priority', 'created')

    def __str__(self):
        return '%s waits for %s' % (str(self.email), str(self.item))

    def clean(self):
        WaitingListEntry.clean_duplicate(self.email, self.item, self.variation,
                                         self.subevent, self.pk)
        WaitingListEntry.clean_itemvar(self.event, self.item, self.variation)
        WaitingListEntry.clean_subevent(self.event, self.subevent)

    def save(self, *args, **kwargs):
        update_fields = kwargs.get('update_fields', [])
        if 'name_parts' in update_fields:
            update_fields.append('name_cached')
        self.name_cached = self.name
        if self.name_parts is None:
            self.name_parts = {}
        super().save(*args, **kwargs)

    @property
    def name(self):
        if not self.name_parts:
            return None
        if '_legacy' in self.name_parts:
            return self.name_parts['_legacy']
        if '_scheme' in self.name_parts:
            scheme = PERSON_NAME_SCHEMES[self.name_parts['_scheme']]
        else:
            scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme]
        return scheme['concatenation'](self.name_parts).strip()

    def send_voucher(self, quota_cache=None, user=None, auth=None):
        availability = (self.variation.check_quotas(count_waitinglist=False,
                                                    subevent=self.subevent,
                                                    _cache=quota_cache)
                        if self.variation else self.item.check_quotas(
                            count_waitinglist=False,
                            subevent=self.subevent,
                            _cache=quota_cache))
        if availability[1] is None or availability[1] < 1:
            raise WaitingListException(
                _('This product is currently not available.'))
        if self.voucher:
            raise WaitingListException(
                _('A voucher has already been sent to this person.'))
        if '@' not in self.email:
            raise WaitingListException(
                _('This entry is anonymized and can no longer be used.'))

        with transaction.atomic():
            v = Voucher.objects.create(
                event=self.event,
                max_usages=1,
                valid_until=now() +
                timedelta(hours=self.event.settings.waiting_list_hours),
                item=self.item,
                variation=self.variation,
                tag='waiting-list',
                comment=_(
                    'Automatically created from waiting list entry for {email}'
                ).format(email=self.email),
                block_quota=True,
                subevent=self.subevent,
            )
            v.log_action(
                'pretix.voucher.added.waitinglist', {
                    'item': self.item.pk,
                    'variation': self.variation.pk if self.variation else None,
                    'tag': 'waiting-list',
                    'block_quota': True,
                    'valid_until': v.valid_until.isoformat(),
                    'max_usages': 1,
                    'email': self.email,
                    'waitinglistentry': self.pk,
                    'subevent': self.subevent.pk if self.subevent else None,
                },
                user=user,
                auth=auth)
            self.log_action('pretix.waitinglist.voucher', user=user, auth=auth)
            self.voucher = v
            self.save()

        with language(self.locale, self.event.settings.region):
            mail(self.email,
                 _('You have been selected from the waitinglist for {event}').
                 format(event=str(self.event)),
                 self.event.settings.mail_text_waiting_list,
                 get_email_context(event=self.event, waiting_list_entry=self),
                 self.event,
                 locale=self.locale)

    @staticmethod
    def clean_itemvar(event, item, variation):
        if event != item.event:
            raise ValidationError(
                _('The selected item does not belong to this event.'))
        if item.has_variations and (not variation or variation.item != item):
            raise ValidationError(
                _('Please select a specific variation of this product.'))

    @staticmethod
    def clean_subevent(event, subevent):
        if event.has_subevents:
            if not subevent:
                raise ValidationError(
                    _('Subevent cannot be null for event series.'))
            if event != subevent.event:
                raise ValidationError(
                    _('The subevent does not belong to this event.'))
        else:
            if subevent:
                raise ValidationError(
                    _('The subevent does not belong to this event.'))

    @staticmethod
    def clean_duplicate(email, item, variation, subevent, pk):
        if WaitingListEntry.objects.filter(
                item=item,
                variation=variation,
                email__iexact=email,
                voucher__isnull=True,
                subevent=subevent).exclude(pk=pk).exists():
            raise ValidationError(
                _('You are already on this waiting list! We will notify '
                  'you as soon as we have a ticket available for you.'))
Example #14
0
class Event(models.Model):
    """
    The Event class is the central class in when.events. It contains all
    data related to an event, and is pretty much a direct mirror of the
    when.events API. Fields are influenced by the schema.org Event model,
    but extended and modified to fit our purposes.
    All fields except the URL are nullable, since they will be empty when
    the URL is added in the first place.
    """

    name = models.CharField(max_length=200, null=True, verbose_name=_("Name"))
    short_name = models.SlugField(null=True,
                                  unique=True,
                                  verbose_name=_("Short name"))

    start_date = models.DateField(null=True, verbose_name=_("Start date"))
    end_date = models.DateField(null=True, verbose_name=_("End date"))
    cfp_deadline = models.DateTimeField(null=True,
                                        verbose_name=_("CfP deadline"))
    timezone = models.CharField(
        choices=[(tz, tz) for tz in pytz.common_timezones],
        max_length=30,
        default="UTC",
        verbose_name=_("Timezone"),
    )

    organizer = models.CharField(max_length=200,
                                 null=True,
                                 verbose_name=_("Organizer"))
    email = models.EmailField(null=True, verbose_name=_("Email"))
    is_accessible_for_free = models.BooleanField(
        default=False, verbose_name=_("Is this event free?"))
    languages = models.CharField(
        max_length=200, null=True, verbose_name=_("Languages")
    )  # Format: ,en,de,. Use 'language_list' property for access.
    maximum_attendee_capacity = models.PositiveIntegerField(
        null=True, verbose_name=_("Maximum attendee capacity"))
    location = models.CharField(max_length=200,
                                null=True,
                                verbose_name=_("Location"))
    coordinates = models.CharField(max_length=50,
                                   null=True,
                                   verbose_name=_("Coordinates"))
    description = models.TextField(null=True, verbose_name=_("Description"))
    color = models.CharField(max_length=6, null=True, verbose_name=_("Color"))

    urls = FallbackJSONField(null=True, default=dict)

    hashtag = models.CharField(max_length=50,
                               null=True,
                               verbose_name=_("Hashtag"))
    social_media_accounts = FallbackJSONField(null=True, default=dict)
    tooling = FallbackJSONField(null=True)

    tags = models.TextField(
        null=True)  # Contains topics, locations, …. Use tag_list to access it.

    # INTERNAL FIELDS #
    data_url = models.URLField()
    state = models.CharField(
        max_length=11,
        choices=(
            ("new", "new"),
            ("ok", "ok"),
            ("unreachable", "unreachable"),
            ("error", "error"),
        ),
        verbose_name=_("State"),
    )
    needs_review = models.BooleanField(default=True)
    was_reviewed = models.BooleanField(default=False)

    @property
    def language_list(self):
        return (self.lanugages or "").strip(",").split(",")

    @language_list.setter
    def language_list(self, value):
        self.languages = "," + ",".join(value) + ","

    @property
    def tag_list(self):
        return (self.tags or "").strip(",").split(",")

    @tag_list.setter
    def tag_list(self, value):
        self.tags = "," + ",".join(value) + ","

    def _update(self, data):
        creating = self.state == 'new'
        field_list = []
        for field, value in data.items():
            if field == 'version':
                continue
            local_name = decamel(field)
            if hasattr(self, local_name[:-1] + '_list'):
                local_name = local_name[:-1] + '_list'
            if not getattr(self, local_name) == value:
                setattr(self, local_name, value)
                field_list.append(field)
        self.state = "ok"
        self.save()
        if creating:
            content = {'action': 'create'}
        else:
            content = {'action': 'update', 'fields': field_list}
        log = Log.objects.create(
            event=self if self.pk else None,
            state=self.state,
            content=content,
            timestamp=now(),
        )
        return log

    def _fail(self, error, state="error", content=None):
        log = Log.objects.create(
            event=self if self.pk else None,
            state=state,
            content={
                'content': content,
                'error': error
            },
            timestamp=now(),
        )
        self.state = state
        self.save()
        return log

    def fetch(self):
        response = requests.get(self.data_url)
        fail = partial(self._fail, content=response.content.decode())

        try:
            response.raise_for_status()
        except Exception:
            return fail(response.status_code, state="unreachable")

        try:
            content = response.json()
        except Exception as e:
            return fail(str(e))

        if content.get("version") not in schema.VERSIONS:
            return fail("Unsupported version. Supported versions are: " +
                        ", ".join(schema.VERSIONS))

        used_schema = schema.get_schema(content.get("version"))
        try:
            jsonschema.validate(
                content,
                used_schema,
                format_checker=jsonschema.draft7_format_checker)
        except Exception as e:
            return fail({
                'path': [p for p in e.path],
                'message': e.message,
            })

        return self._update(content)
Example #15
0
class HistoryModel(DirtyFieldsMixin, models.Model):
    id = models.UUIDField(primary_key=True,
                          db_column="UUID",
                          default=None,
                          editable=False)
    objects = HistoryModelManager()
    is_deleted = models.BooleanField(db_column="isDeleted", default=False)
    json_ext = FallbackJSONField(db_column="Json_ext", blank=True, null=True)
    date_created = DateTimeField(db_column="DateCreated", null=True)
    date_updated = DateTimeField(db_column="DateUpdated", null=True)
    user_created = models.ForeignKey(User,
                                     db_column="UserCreatedUUID",
                                     related_name='%(class)s_user_created',
                                     on_delete=models.deletion.DO_NOTHING,
                                     null=False)
    user_updated = models.ForeignKey(User,
                                     db_column="UserUpdatedUUID",
                                     related_name='%(class)s_user_updated',
                                     on_delete=models.deletion.DO_NOTHING,
                                     null=False)
    version = models.IntegerField(default=1)
    history = HistoricalRecords(inherit=True, )

    @property
    def uuid(self):
        return self.id

    @uuid.setter
    def uuid(self, v):
        self.id = v

    def set_pk(self):
        self.pk = uuid.uuid4()

    def save_history(self):
        pass

    def save(self, *args, **kwargs):
        # get the user data so as to assign later his uuid id in fields user_updated etc
        user = User.objects.get(**kwargs)
        from core import datetime
        now = datetime.datetime.now()
        # check if object has been newly created
        if self.id is None:
            # save the new object
            self.set_pk()
            self.user_created = user
            self.user_updated = user
            self.date_created = now
            self.date_updated = now
            return super(HistoryModel, self).save()
        if self.is_dirty(check_relationship=True):
            self.date_updated = now
            self.user_updated = user
            self.version = self.version + 1
            # check if we have business model
            if hasattr(self, "replacement_uuid"):
                if self.replacement_uuid is not None and 'replacement_uuid' not in self.get_dirty_fields(
                ):
                    raise ValidationError(
                        'Update error! You cannot update replaced entity')
            return super(HistoryModel, self).save()
        else:
            raise ValidationError(
                'Record has not be updated - there are no changes in fields')

    def delete_history(self):
        pass

    def delete(self, *args, **kwargs):
        user = User.objects.get(**kwargs)
        if not self.is_dirty(check_relationship=True) and not self.is_deleted:
            from core import datetime
            now = datetime.datetime.now()
            self.date_updated = now
            self.user_updated = user
            self.version = self.version + 1
            self.is_deleted = True
            # check if we have business model
            if hasattr(self, "replacement_uuid"):
                # When a replacement entity is deleted, the link should be removed
                # from replaced entity so a new replacement could be generated
                replaced_entity = self.__class__.objects.filter(
                    replacement_uuid=self.id).first()
                if replaced_entity:
                    replaced_entity.replacement_uuid = None
                    replaced_entity.save(username="******")
            return super(HistoryModel, self).save()
        else:
            raise ValidationError(
                'Record has not be deactivating, the object is different and must be updated before deactivating'
            )

    @classmethod
    def filter_queryset(cls, queryset=None):
        if queryset is None:
            queryset = cls.objects.all()
        queryset = queryset.filter()
        return queryset

    class Meta:
        abstract = True
Example #16
0
class ExtendableModel(models.Model):
    json_ext = FallbackJSONField(db_column='JsonExt', blank=True, null=True)

    class Meta:
        abstract = True
Example #17
0
class Node(models.Model):
    """
    A document node.

    Could be any type of node e.g. `CodeChunk`, `CreativeWork`, `Number`.

    Each node has a unique `key` generated at the time of creation. This
    is the only way to retreive a node.

    Each node can be associated with a `project`. This is for authorization.
    Although the `key` is a secret, project based authorization adds an additional
    layer of security e.g. in case of accidental leakage of a node URL.
    This field does not use cascading delete because node URLs
    should be permananent. The `project` is not required. This allows
    apps to create nodes in documents (e.g. GSuita) or to or to convert documents
    (e.g. Encoda) without having to associate them with a project.

    Each node is created by an `app`. This string is primarily used when generating
    HTML representations of the node to provide links back to that app.

    A node is usually created within a `host`. This is a URL that is primarily used
    when generating HTML representations of the node to provide links back to the
    document.

    The `json` of the node is also immutable. It is returned to requests with
    `Accept: application/json` (if authorized).
    """
    class Meta:
        unique_together = (
            "project",
            "key",
        )

    creator = models.ForeignKey(
        User,
        null=True,  # Should only be null if the creator is deleted
        blank=True,
        on_delete=models.SET_NULL,
        related_name="nodes_created",
        help_text="User who created the project.",
    )

    created = models.DateTimeField(auto_now_add=True,
                                   help_text="When the project was created.")

    project = models.ForeignKey(
        Project,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="nodes",
        help_text="The project this node is associated with.",
    )

    app = models.TextField(
        null=True,
        blank=True,
        help_text="An identifier for the app that created the node.",
    )

    host = models.URLField(
        null=True,
        blank=True,
        help_text="URL of the host document within which the node was created.",
    )

    key = models.TextField(db_index=True, help_text="The key to the node")

    json = FallbackJSONField(help_text="The JSON content of node.")

    def get_absolute_url(self):
        return reverse("api-nodes-detail", kwargs={"key": self.key})