Store all check-in attempts, not only successful ones (#2074)

This commit is contained in:
Raphael Michel
2021-06-05 13:00:58 +02:00
committed by GitHub
parent 9c3fc69176
commit c7ef79be90
29 changed files with 849 additions and 66 deletions

View File

@@ -31,6 +31,7 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
from datetime import timedelta
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -231,9 +232,14 @@ class CheckinList(LoggedModel):
return rules
class SuccessfulCheckinManager(ScopedManager(organizer='list__event__organizer').__class__):
def get_queryset(self):
return super().get_queryset().filter(successful=True)
class Checkin(models.Model):
"""
A check-in object is created when a person enters or exits the event.
A check-in object is created when a ticket is scanned with our scanning apps.
"""
TYPE_ENTRY = 'entry'
TYPE_EXIT = 'exit'
@@ -241,13 +247,82 @@ class Checkin(models.Model):
(TYPE_ENTRY, _('Entry')),
(TYPE_EXIT, _('Exit')),
)
position = models.ForeignKey('pretixbase.OrderPosition', related_name='checkins', on_delete=models.CASCADE)
REASON_CANCELED = 'canceled'
REASON_INVALID = 'invalid'
REASON_UNPAID = 'unpaid'
REASON_PRODUCT = 'product'
REASON_RULES = 'rules'
REASON_REVOKED = 'revoked'
REASON_INCOMPLETE = 'incomplete'
REASON_ALREADY_REDEEMED = 'already_redeemed'
REASON_ERROR = 'error'
REASONS = (
(REASON_CANCELED, _('Order canceled')),
(REASON_INVALID, _('Unknown ticket')),
(REASON_UNPAID, _('Ticket not paid')),
(REASON_RULES, _('Forbidden by custom rule')),
(REASON_REVOKED, _('Ticket code revoked/changed')),
(REASON_INCOMPLETE, _('Information required')),
(REASON_ALREADY_REDEEMED, _('Ticket already used')),
(REASON_ERROR, _('Server error')),
)
successful = models.BooleanField(
default=True,
)
error_reason = models.CharField(
max_length=100,
choices=REASONS,
null=True,
blank=True,
)
error_explanation = models.TextField(
null=True,
blank=True,
)
position = models.ForeignKey(
'pretixbase.OrderPosition',
related_name='all_checkins',
on_delete=models.CASCADE,
null=True, blank=True,
)
# For "raw" scans where we do not know which position they belong to (e.g. scan of signed
# barcode that is not in database).
raw_barcode = models.TextField(null=True, blank=True)
raw_item = models.ForeignKey(
'pretixbase.Item',
related_name='checkins',
on_delete=models.SET_NULL,
null=True, blank=True,
)
raw_variation = models.ForeignKey(
'pretixbase.ItemVariation',
related_name='checkins',
on_delete=models.SET_NULL,
null=True, blank=True,
)
raw_subevent = models.ForeignKey(
'pretixbase.SubEvent',
related_name='checkins',
on_delete=models.SET_NULL,
null=True, blank=True,
)
# Datetime of checkin, might be different from created if past scans are uploaded
datetime = models.DateTimeField(default=now)
nonce = models.CharField(max_length=190, null=True, blank=True)
# Datetime of creation on server
created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
list = models.ForeignKey(
'pretixbase.CheckinList', related_name='checkins', on_delete=models.PROTECT,
)
type = models.CharField(max_length=100, choices=CHECKIN_TYPES, default=TYPE_ENTRY)
nonce = models.CharField(max_length=190, null=True, blank=True)
forced = models.BooleanField(default=False)
device = models.ForeignKey(
'pretixbase.Device', related_name='checkins', on_delete=models.PROTECT, null=True, blank=True
@@ -257,7 +332,8 @@ class Checkin(models.Model):
)
auto_checked_in = models.BooleanField(default=False)
objects = ScopedManager(organizer='position__order__event__organizer')
all = ScopedManager(organizer='list__event__organizer')
objects = SuccessfulCheckinManager()
class Meta:
ordering = (('-datetime'),)
@@ -269,7 +345,8 @@ class Checkin(models.Model):
def save(self, **kwargs):
super().save(**kwargs)
self.position.order.touch()
if self.position:
self.position.order.touch()
self.list.event.cache.delete('checkin_count')
self.list.touch()
@@ -277,3 +354,7 @@ class Checkin(models.Model):
super().delete(**kwargs)
self.position.order.touch()
self.list.touch()
@property
def is_late_upload(self):
return self.created and abs(self.created - self.datetime) > timedelta(minutes=2)

View File

@@ -2054,6 +2054,14 @@ class OrderPosition(AbstractPosition):
def sort_key(self):
return self.addon_to.positionid if self.addon_to else self.positionid, self.addon_to_id or 0
@property
def checkins(self):
"""
Related manager for all successful checkins. Use ``all_checkins`` instead if you want
canceled positions as well.
"""
return self.all_checkins(manager='objects')
@property
def generate_ticket(self):
if self.item.generate_tickets is not None: