mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
@@ -7,6 +7,7 @@ from .event import (
|
||||
Event, Event_SettingsStore, EventLock, EventMetaProperty, EventMetaValue,
|
||||
RequiredAction, SubEvent, SubEventMetaValue, generate_invite_token,
|
||||
)
|
||||
from .giftcards import GiftCard, GiftCardAcceptance, GiftCardTransaction
|
||||
from .invoices import Invoice, InvoiceLine, invoice_filename
|
||||
from .items import (
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation, Question,
|
||||
|
||||
@@ -335,6 +335,25 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
| Q(id__in=self.teams.filter(**kwargs).values_list('limit_events__id', flat=True))
|
||||
)
|
||||
|
||||
@scopes_disabled()
|
||||
def get_organizers_with_permission(self, permission, request=None):
|
||||
"""
|
||||
Returns a queryset of organizers the user has a specific permissions to.
|
||||
|
||||
:param request: The current request (optional). Required to detect staff sessions properly.
|
||||
:return: Iterable of Organizers
|
||||
"""
|
||||
from .event import Organizer
|
||||
|
||||
if request and self.has_active_staff_session(request.session.session_key):
|
||||
return Organizer.objects.all()
|
||||
|
||||
kwargs = {permission: True}
|
||||
|
||||
return Organizer.objects.filter(
|
||||
id__in=self.teams.filter(**kwargs).values_list('organizer', flat=True)
|
||||
)
|
||||
|
||||
def has_active_staff_session(self, session_key=None):
|
||||
"""
|
||||
Returns whether or not a user has an active staff session (formerly known as superuser session)
|
||||
|
||||
@@ -730,7 +730,7 @@ class Event(EventMixin, LoggedModel):
|
||||
def has_payment_provider(self):
|
||||
result = False
|
||||
for provider in self.get_payment_providers().values():
|
||||
if provider.is_enabled and provider.identifier not in ('free', 'boxoffice', 'offsetting'):
|
||||
if provider.is_enabled and provider.identifier not in ('free', 'boxoffice', 'offsetting', 'giftcard'):
|
||||
result = True
|
||||
break
|
||||
return result
|
||||
|
||||
112
src/pretix/base/models/giftcards.py
Normal file
112
src/pretix/base/models/giftcards.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.banlist import banned
|
||||
from pretix.base.models import LoggedModel
|
||||
|
||||
|
||||
def gen_giftcard_secret():
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
|
||||
while True:
|
||||
code = get_random_string(length=settings.ENTROPY['giftcard_secret'], allowed_chars=charset)
|
||||
if not banned(code) and not GiftCard.objects.filter(secret=code).exists():
|
||||
return code
|
||||
|
||||
|
||||
class GiftCardAcceptance(models.Model):
|
||||
issuer = models.ForeignKey(
|
||||
'Organizer',
|
||||
related_name='gift_card_collector_acceptance',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
collector = models.ForeignKey(
|
||||
'Organizer',
|
||||
related_name='gift_card_issuer_acceptance',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
|
||||
class GiftCard(LoggedModel):
|
||||
issuer = models.ForeignKey(
|
||||
'Organizer',
|
||||
related_name='issued_gift_cards',
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
issued_in = models.ForeignKey(
|
||||
'OrderPosition',
|
||||
related_name='issued_gift_cards',
|
||||
on_delete=models.PROTECT,
|
||||
null=True, blank=True
|
||||
)
|
||||
issuance = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
)
|
||||
secret = models.CharField(
|
||||
max_length=190,
|
||||
default=gen_giftcard_secret,
|
||||
db_index=True,
|
||||
verbose_name=_('Gift card code'),
|
||||
)
|
||||
testmode = models.BooleanField(
|
||||
verbose_name=_('Test mode card'),
|
||||
default=False
|
||||
)
|
||||
CURRENCY_CHOICES = [(c.alpha_3, c.alpha_3 + " - " + c.name) for c in settings.CURRENCIES]
|
||||
currency = models.CharField(max_length=10, choices=CURRENCY_CHOICES)
|
||||
|
||||
def __str__(self):
|
||||
return self.secret
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.transactions.aggregate(s=Sum('value'))['s'] or Decimal('0.00')
|
||||
|
||||
def accepted_by(self, organizer):
|
||||
return self.issuer == organizer or GiftCardAcceptance.objects.filter(issuer=self.issuer, collector=organizer).exists()
|
||||
|
||||
class Meta:
|
||||
unique_together = (('secret', 'issuer'),)
|
||||
|
||||
|
||||
class GiftCardTransaction(models.Model):
|
||||
card = models.ForeignKey(
|
||||
'GiftCard',
|
||||
related_name='transactions',
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
datetime = models.DateTimeField(
|
||||
auto_now_add=True
|
||||
)
|
||||
value = models.DecimalField(
|
||||
decimal_places=2,
|
||||
max_digits=10
|
||||
)
|
||||
order = models.ForeignKey(
|
||||
'Order',
|
||||
related_name='gift_card_transactions',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
payment = models.ForeignKey(
|
||||
'OrderPayment',
|
||||
related_name='gift_card_transactions',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
refund = models.ForeignKey(
|
||||
'OrderRefund',
|
||||
related_name='gift_card_transactions',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ("datetime",)
|
||||
@@ -242,6 +242,8 @@ class Item(LoggedModel):
|
||||
:type require_approval: bool
|
||||
:param sales_channels: Sales channels this item is available on.
|
||||
:type sales_channels: bool
|
||||
:param issue_giftcard: If ``True``, buying this product will give you a gift card with the value of the product's price
|
||||
:type issue_giftcard: bool
|
||||
"""
|
||||
|
||||
objects = ItemQuerySetManager()
|
||||
@@ -413,6 +415,12 @@ class Item(LoggedModel):
|
||||
verbose_name=_('Sales channels'),
|
||||
default=['web']
|
||||
)
|
||||
issue_giftcard = models.BooleanField(
|
||||
verbose_name=_('This product is a gift card'),
|
||||
help_text=_('When a customer buys this product, they will get a gift card with a value corresponding to the '
|
||||
'product price.'),
|
||||
default=False
|
||||
)
|
||||
# !!! Attention: If you add new fields here, also add them to the copying code in
|
||||
# pretix/control/forms/item.py if applicable.
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ class Order(LockModel, LoggedModel):
|
||||
return self.full_code
|
||||
|
||||
def gracefully_delete(self, user=None, auth=None):
|
||||
from . import Voucher
|
||||
from . import Voucher, GiftCard, GiftCardTransaction
|
||||
|
||||
if not self.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
@@ -218,6 +218,10 @@ class Order(LockModel, LoggedModel):
|
||||
if position.voucher:
|
||||
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') - 1))
|
||||
|
||||
GiftCardTransaction.objects.filter(payment__in=self.payments.all()).update(payment=None)
|
||||
GiftCardTransaction.objects.filter(refund__in=self.refunds.all()).update(refund=None)
|
||||
GiftCardTransaction.objects.filter(order=self).update(order=None)
|
||||
GiftCard.objects.filter(issued_in__in=self.positions.all()).update(issued_in=None)
|
||||
OrderPosition.all.filter(order=self, addon_to__isnull=False).delete()
|
||||
OrderPosition.all.filter(order=self).delete()
|
||||
OrderFee.all.filter(order=self).delete()
|
||||
@@ -460,11 +464,15 @@ class Order(LockModel, LoggedModel):
|
||||
positions = list(
|
||||
self.positions.all().annotate(
|
||||
has_checkin=Exists(Checkin.objects.filter(position_id=OuterRef('pk')))
|
||||
).select_related('item')
|
||||
).select_related('item').prefetch_related('issued_gift_cards')
|
||||
)
|
||||
cancelable = all([op.item.allow_cancel and not op.has_checkin for op in positions])
|
||||
if not cancelable or not positions:
|
||||
return False
|
||||
for op in positions:
|
||||
for gc in op.issued_gift_cards.all():
|
||||
if gc.value != op.price:
|
||||
return False
|
||||
if self.user_cancel_deadline and now() > self.user_cancel_deadline:
|
||||
return False
|
||||
if self.status == Order.STATUS_PENDING:
|
||||
|
||||
@@ -2,6 +2,7 @@ import string
|
||||
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@@ -82,6 +83,24 @@ class Organizer(LoggedModel):
|
||||
|
||||
return ObjectRelatedCache(self)
|
||||
|
||||
@property
|
||||
def has_gift_cards(self):
|
||||
return self.cache.get_or_set(
|
||||
key='has_gift_cards',
|
||||
timeout=15,
|
||||
default=lambda: self.issued_gift_cards.exists() or self.gift_card_issuer_acceptance.exists()
|
||||
)
|
||||
|
||||
@property
|
||||
def accepted_gift_cards(self):
|
||||
from .giftcards import GiftCard, GiftCardAcceptance
|
||||
|
||||
return GiftCard.objects.annotate(
|
||||
accepted=Exists(GiftCardAcceptance.objects.filter(issuer=OuterRef('issuer'), collector=self))
|
||||
).filter(
|
||||
Q(issuer=self) | Q(accepted=True)
|
||||
)
|
||||
|
||||
def allow_delete(self):
|
||||
from . import Order, Invoice
|
||||
return (
|
||||
@@ -156,6 +175,10 @@ class Team(LoggedModel):
|
||||
help_text=_('Someone with this setting can get access to most data of all of your events, i.e. via privacy '
|
||||
'reports, so be careful who you add to this team!')
|
||||
)
|
||||
can_manage_gift_cards = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("Can manage gift cards")
|
||||
)
|
||||
|
||||
can_change_event_settings = models.BooleanField(
|
||||
default=False,
|
||||
|
||||
Reference in New Issue
Block a user