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'
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().", )
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'
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)
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
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)
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)
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
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
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)
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']
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.'))
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)
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
class ExtendableModel(models.Model): json_ext = FallbackJSONField(db_column='JsonExt', blank=True, null=True) class Meta: abstract = True
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})