diff --git a/doc/development/concepts.rst b/doc/development/concepts.rst index 1baf608229..ca3548b7dc 100644 --- a/doc/development/concepts.rst +++ b/doc/development/concepts.rst @@ -28,9 +28,9 @@ Users and events Pretix is all about **events**, which are defined as something happening somewhere. Every event is managed by the **organizer**, an abstract entity running the event. -Pretix is used by **users**. We want to enable global users who can just login into -pretix and buy tickets for as many events as they like but at the same time it -should be possible to order products **without** needing an user account. +Pretix has a concept of **users** that is used for all the people who have to log +in to the control panel to manage one or more events. No user is required to place an +order. Items and variations diff --git a/src/pretix/base/migrations/0019_auto_20151004_1233.py b/src/pretix/base/migrations/0019_auto_20151004_1233.py new file mode 100644 index 0000000000..c4cc680df2 --- /dev/null +++ b/src/pretix/base/migrations/0019_auto_20151004_1233.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0018_eventlock_token'), + ] + + operations = [ + migrations.RenameField( + model_name='order', + old_name='guest_email', + new_name='email', + ), + migrations.RenameField( + model_name='order', + old_name='guest_locale', + new_name='locale', + ), + migrations.RemoveField( + model_name='cartposition', + name='user', + ), + migrations.RemoveField( + model_name='order', + name='user', + ), + ] diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index 5093742d35..a606f94047 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -1366,7 +1366,7 @@ class Quota(Versionable): def generate_secret(): - return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32)) + return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16)) class Order(Versionable): @@ -1393,8 +1393,10 @@ class Order(Versionable): :param event: The event this belongs to :type event: Event - :param user: The user who ordered this - :type user: User + :param email: The email of the person who ordered this + :type email: str + :param locale: The locale of this order + :type locale: str :param datetime: The datetime of the order placement :type datetime: datetime :param expires: The date until this order has to be paid to guarantee the @@ -1438,16 +1440,11 @@ class Order(Versionable): verbose_name=_("Event"), related_name="orders" ) - user = models.ForeignKey( - User, null=True, blank=True, - verbose_name=_("User"), - related_name="orders" - ) - guest_email = models.EmailField( + email = models.EmailField( null=True, blank=True, verbose_name=_('E-mail') ) - guest_locale = models.CharField( + locale = models.CharField( null=True, blank=True, max_length=32, verbose_name=_('Locale') ) @@ -1592,18 +1589,6 @@ class Order(Versionable): return str(e) return True - @property - def locale(self): - if self.user: - return self.user.locale - return self.guest_locale - - @property - def email(self): - if self.user: - return self.user.email - return self.guest_email - class CachedTicket(models.Model): order = VersionedForeignKey(Order, on_delete=models.CASCADE) @@ -1736,8 +1721,8 @@ class CartPosition(ObjectWithAnswers, Versionable): :type event: Evnt :param item: The selected item :type item: Item - :param user: The user who has this in his cart - :type user: User + :param session: The user session that contains this cart position + :type session: str :param variation: The selected ItemVariation or null, if the item has no properties :type variation: ItemVariation :param datetime: The datetime this item was put into the cart @@ -1753,10 +1738,6 @@ class CartPosition(ObjectWithAnswers, Versionable): Event, verbose_name=_("Event") ) - user = models.ForeignKey( - User, null=True, blank=True, - verbose_name=_("User") - ) session = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Session") diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py index d8c410aa95..7477b42264 100644 --- a/src/pretix/base/payment.py +++ b/src/pretix/base/payment.py @@ -10,10 +10,9 @@ from django.http import HttpRequest from django.template.loader import get_template from django.utils.translation import ugettext_lazy as _ -from pretix.base.models import CartPosition, Order +from pretix.base.models import CartPosition, Order, Quota from pretix.base.settings import SettingsSandbox from pretix.base.signals import register_payment_providers -from pretix.presale.views import user_cart_q class BasePaymentProvider: @@ -411,7 +410,10 @@ class FreeOrderProvider(BasePaymentProvider): def payment_perform(self, request: HttpRequest, order: Order): from pretix.base.services.orders import mark_order_paid - mark_order_paid(order, 'free') + try: + mark_order_paid(order, 'free') + except Quota.QuotaExceededException as e: + messages.error(request, str(e)) @property def settings_form_fields(self) -> dict: @@ -442,7 +444,7 @@ class FreeOrderProvider(BasePaymentProvider): def is_allowed(self, request: HttpRequest) -> bool: return CartPosition.objects.current.filter( - user_cart_q(request) & Q(event=request.event) + session=request.session.session_key, event=request.event ).aggregate(sum=Sum('price'))['sum'] == 0 diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index a42947a503..bd854e55ec 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -5,7 +5,7 @@ from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from pretix.base.models import ( - CartPosition, Event, EventLock, Item, ItemVariation, Quota, User, + CartPosition, Event, EventLock, Item, ItemVariation, Quota, ) @@ -28,27 +28,21 @@ error_messages = { } -def _user_cart_q(user=None, guest_session=None): - if user and user.is_authenticated(): - return Q(Q(user=user) | Q(session=guest_session)) - return Q(Q(user__isnull=True) & Q(session=guest_session)) - - -def _extend_existing(event, user, guest_session, expiry): +def _extend_existing(event, session, expiry): # Extend this user's cart session to 30 minutes from now to ensure all items in the # cart expire at the same time # We can extend the reservation of items which are not yet expired without risk CartPosition.objects.current.filter( - _user_cart_q(user, guest_session) & Q(event=event) & Q(expires__gt=now()) + Q(session=session) & Q(event=event) & Q(expires__gt=now()) ).update(expires=expiry) -def _re_add_expired_positions(items, event, user, guest_session): +def _re_add_expired_positions(items, event, session): positions = set() # For items that are already expired, we have to delete and re-add them, as they might # be no longer available or prices might have changed. Sorry! expired = CartPosition.objects.current.filter( - _user_cart_q(user, guest_session) & Q(event=event) & Q(expires__lte=now()) + Q(session=session) & Q(event=event) & Q(expires__lte=now()) ) for cp in expired: items.insert(0, (cp.item_id, cp.variation_id, 1, cp)) @@ -69,7 +63,7 @@ def _check_date(event): raise CartError(error_messages['ended']) -def _add_items(event, items, user, guest_session, expiry): +def _add_items(event, items, session, expiry): err = None # Fetch items from the database @@ -129,40 +123,36 @@ def _add_items(event, items, user, guest_session, expiry): else: CartPosition.objects.create( event=event, item=item, variation=variation, price=price, expires=expiry, - user=user if user and user.is_authenticated() else None, - session=guest_session if not user or not user.is_authenticated() else None + session=session ) return err -def add_items_to_cart(event: str, items: list, user: int=None, guest_session: str=None): +def add_items_to_cart(event: str, items: list, session: str=None): """ - Adds a list of items to a user's or a guest's cart. + Adds a list of items to a user's cart. :param event: The event ID in question :param items: A list of tuple of the form (item id, variation id or None, number) - :param user: User ID - :param guest_session: Session ID of a guest + :param session: Session ID of a guest :raises CartError: On any error that occured """ - if user: - user = User.objects.get(id=user) event = Event.objects.current.get(identity=event) try: with event.lock(): _check_date(event) - existing = CartPosition.objects.current.filter(_user_cart_q(user, guest_session) & Q(event=event)).count() + existing = CartPosition.objects.current.filter(Q(session=session) & Q(event=event)).count() if sum(i[2] for i in items) + existing > int(event.settings.max_items_per_order): # TODO: i18n plurals raise CartError(error_messages['max_items'] % event.settings.max_items_per_order) expiry = now() + timedelta(minutes=event.settings.get('reservation_time', as_type=int)) - _extend_existing(event, user, guest_session, expiry) + _extend_existing(event, session, expiry) - expired = _re_add_expired_positions(items, event, user, guest_session) + expired = _re_add_expired_positions(items, event, session) if not items: raise CartError(error_messages['empty']) - err = _add_items(event, items, user, guest_session, expiry) + err = _add_items(event, items, session, expiry) _delete_expired(expired) if err: raise CartError(err) @@ -170,20 +160,17 @@ def add_items_to_cart(event: str, items: list, user: int=None, guest_session: st raise CartError(error_messages['busy']) -def remove_items_from_cart(event: str, items: list, user: int=None, guest_session: str=None): +def remove_items_from_cart(event: str, items: list, session: str=None): """ - Removes a list of items from a user's or a guest's cart. + Removes a list of items from a user's cart. :param event: The event ID in question :param items: A list of tuple of the form (item id, variation id or None, number) - :param user: User ID - :param guest_session: Session ID of a guest + :param session: Session ID of a guest """ - if user: - user = User.objects.get(id=user) event = Event.objects.current.get(identity=event) for item, variation, cnt in items: - cw = _user_cart_q(user, guest_session) & Q(item_id=item) & Q(event=event) + cw = Q(session=session) & Q(item_id=item) & Q(event=event) if variation: cw &= Q(variation_id=variation) else: diff --git a/src/pretix/base/services/mail.py b/src/pretix/base/services/mail.py index b5337df535..a91471279b 100644 --- a/src/pretix/base/services/mail.py +++ b/src/pretix/base/services/mail.py @@ -17,7 +17,7 @@ def mail(email: str, subject: str, template: str, context: dict=None, event: Eve """ Sends out an email to a user. - :param user: The user this should be sent to. + :param email: The e-mail this should be sent to. :param subject: The e-mail subject. Should be localized. :param template: The filename of a template to be used. It will be rendered with the recipient's locale. Alternatively, you @@ -31,7 +31,8 @@ def mail(email: str, subject: str, template: str, context: dict=None, event: Eve backend. """ _lng = translation.get_language() - translation.activate(locale or settings.LANGUAGE_CODE) + if locale: + translation.activate(locale or settings.LANGUAGE_CODE) if isinstance(template, LazyI18nString): body = str(template) @@ -52,17 +53,6 @@ def mail(email: str, subject: str, template: str, context: dict=None, event: Eve "You are receiving this e-mail because you placed an order for %s." % event.name ) body += "\r\n" - body += _( - "You can view all of your orders at the following URL:" - ) - body += "\r\n" - body += build_absolute_uri( - 'presale:event.orders', kwargs={ - 'event': event.slug, - 'organizer': event.organizer.slug - } - ) - body += "\r\n" try: return mail_send([email], subject, body, sender) finally: diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index c4838f5107..011da66025 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -67,7 +67,8 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date 'event': order.event.slug, 'organizer': order.event.organizer.slug, 'order': order.code, - }) + '?order_secret=' + order.secret, + 'secret': order.secret + }), 'downloads': order.event.settings.get('ticket_download', as_type=bool) }, order.event, locale=order.locale @@ -131,14 +132,14 @@ def check_positions(event: Event, dt: datetime, positions: list): raise OrderError(err) -def perform_order(event: Event, payment_provider: BasePaymentProvider, positions: list, user: User=None, +def perform_order(event: Event, payment_provider: BasePaymentProvider, positions: list, email: str=None, locale: str=None): dt = now() try: with event.lock(): check_positions(event, dt, positions) - order = place_order(event, user, email if user is None else None, positions, dt, payment_provider, + order = place_order(event, email, positions, dt, payment_provider, locale=locale) mail( order.email, _('Your order: %(code)s') % {'code': order.code}, @@ -150,7 +151,8 @@ def perform_order(event: Event, payment_provider: BasePaymentProvider, positions 'event': event.slug, 'organizer': event.organizer.slug, 'order': order.code, - }) + '?order_secret=' + order.secret, + 'secret': order.secret + }), 'payment': payment_provider.order_pending_mail_render(order) }, event, locale=order.locale @@ -163,7 +165,7 @@ def perform_order(event: Event, payment_provider: BasePaymentProvider, positions @transaction.atomic() -def place_order(event: Event, user: User, email: str, positions: list, dt: datetime, +def place_order(event: Event, email: str, positions: list, dt: datetime, payment_provider: BasePaymentProvider, locale: str=None): total = sum([c.price for c in positions]) payment_fee = payment_provider.calculate_fee(total) @@ -174,8 +176,7 @@ def place_order(event: Event, user: User, email: str, positions: list, dt: datet order = Order.objects.create( status=Order.STATUS_PENDING, event=event, - user=user, - guest_email=email, + email=email, datetime=dt, expires=min(expires), locale=locale, diff --git a/src/pretix/control/templates/pretixcontrol/order/index.html b/src/pretix/control/templates/pretixcontrol/order/index.html index b36f78a8df..f8abebc276 100644 --- a/src/pretix/control/templates/pretixcontrol/order/index.html +++ b/src/pretix/control/templates/pretixcontrol/order/index.html @@ -67,7 +67,7 @@
{% trans "You need to login or register to continue" %}
-