From e78013c87cd934b577949147b3efa7dd12e1a303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Fri, 29 Mar 2019 22:19:49 +0100 Subject: [PATCH] Blackness. --- src/shop/factories.py | 26 ++- src/shop/forms.py | 13 +- src/shop/models.py | 370 +++++++++++++++++++++--------------------- 3 files changed, 202 insertions(+), 207 deletions(-) diff --git a/src/shop/factories.py b/src/shop/factories.py index 9b0c5e0c..3c65c43f 100644 --- a/src/shop/factories.py +++ b/src/shop/factories.py @@ -11,39 +11,37 @@ from utils.factories import UserFactory class ProductCategoryFactory(DjangoModelFactory): class Meta: - model = 'shop.ProductCategory' + model = "shop.ProductCategory" - name = factory.Faker('word') + name = factory.Faker("word") class ProductFactory(DjangoModelFactory): class Meta: - model = 'shop.Product' + model = "shop.Product" - name = factory.Faker('word') - slug = factory.Faker('word') + name = factory.Faker("word") + slug = factory.Faker("word") category = factory.SubFactory(ProductCategoryFactory) - description = factory.Faker('paragraph') - price = factory.Faker('pyint') + description = factory.Faker("paragraph") + price = factory.Faker("pyint") available_in = factory.LazyFunction( - lambda: - DateTimeTZRange( - lower=timezone.now(), - upper=timezone.now() + timezone.timedelta(31) - ) + lambda: DateTimeTZRange( + lower=timezone.now(), upper=timezone.now() + timezone.timedelta(31) + ) ) class OrderFactory(DjangoModelFactory): class Meta: - model = 'shop.Order' + model = "shop.Order" user = factory.SubFactory(UserFactory) class OrderProductRelationFactory(DjangoModelFactory): class Meta: - model = 'shop.OrderProductRelation' + model = "shop.OrderProductRelation" product = factory.SubFactory(ProductFactory) order = factory.SubFactory(OrderFactory) diff --git a/src/shop/forms.py b/src/shop/forms.py index 43619cbb..9df1ce2c 100644 --- a/src/shop/forms.py +++ b/src/shop/forms.py @@ -5,27 +5,22 @@ from shop.models import OrderProductRelation class OrderProductRelationForm(forms.ModelForm): - class Meta: model = OrderProductRelation - fields = ['quantity'] + fields = ["quantity"] def clean_quantity(self): product = self.instance.product - new_quantity = self.cleaned_data['quantity'] + new_quantity = self.cleaned_data["quantity"] if product.stock_amount and product.left_in_stock < new_quantity: raise forms.ValidationError( - "Only {} left in stock.".format( - product.left_in_stock, - ) + "Only {} left in stock.".format(product.left_in_stock) ) return new_quantity OrderProductRelationFormSet = modelformset_factory( - OrderProductRelation, - form=OrderProductRelationForm, - extra=0 + OrderProductRelation, form=OrderProductRelationForm, extra=0 ) diff --git a/src/shop/models.py b/src/shop/models.py index c4f6f5c3..d35030e6 100644 --- a/src/shop/models.py +++ b/src/shop/models.py @@ -22,177 +22,188 @@ logger = logging.getLogger("bornhack.%s" % __name__) class CustomOrder(CreatedUpdatedModel): - text = models.TextField( - help_text=_('The invoice text') - ) + text = models.TextField(help_text=_("The invoice text")) - customer = models.TextField( - help_text=_('The customer info for this order') - ) + customer = models.TextField(help_text=_("The customer info for this order")) amount = models.IntegerField( - help_text=_('Amount of this custom order (in DKK, including VAT).') + help_text=_("Amount of this custom order (in DKK, including VAT).") ) paid = models.BooleanField( - verbose_name=_('Paid?'), - help_text=_('Check when this custom order has been paid (or if it gets cancelled out by a Credit Note)'), + verbose_name=_("Paid?"), + help_text=_( + "Check when this custom order has been paid (or if it gets cancelled out by a Credit Note)" + ), default=False, ) - danish_vat = models.BooleanField( - help_text="Danish VAT?", - default=True - ) + danish_vat = models.BooleanField(help_text="Danish VAT?", default=True) def __str__(self): - return 'custom order id #%s' % self.pk + return "custom order id #%s" % self.pk @property def vat(self): if self.danish_vat: - return Decimal(round(self.amount*Decimal(0.2), 2)) + return Decimal(round(self.amount * Decimal(0.2), 2)) else: return 0 class Order(CreatedUpdatedModel): class Meta: - unique_together = ('user', 'open') - ordering = ['-created'] + unique_together = ("user", "open") + ordering = ["-created"] products = models.ManyToManyField( - 'shop.Product', - through='shop.OrderProductRelation' + "shop.Product", through="shop.OrderProductRelation" ) user = models.ForeignKey( - 'auth.User', - verbose_name=_('User'), - help_text=_('The user this shop order belongs to.'), - related_name='orders', + "auth.User", + verbose_name=_("User"), + help_text=_("The user this shop order belongs to."), + related_name="orders", on_delete=models.PROTECT, ) paid = models.BooleanField( - verbose_name=_('Paid?'), - help_text=_('Whether this shop order has been paid.'), + verbose_name=_("Paid?"), + help_text=_("Whether this shop order has been paid."), default=False, ) # We are using a NullBooleanField here to ensure that we only have one open order per user at a time. # This "hack" is possible since postgres treats null values as different, and thus we have database level integrity. open = models.NullBooleanField( - verbose_name=_('Open?'), + verbose_name=_("Open?"), help_text=_('Whether this shop order is open or not. "None" means closed.'), default=True, ) - CREDIT_CARD = 'credit_card' - BLOCKCHAIN = 'blockchain' - BANK_TRANSFER = 'bank_transfer' - CASH = 'cash' + CREDIT_CARD = "credit_card" + BLOCKCHAIN = "blockchain" + BANK_TRANSFER = "bank_transfer" + CASH = "cash" - PAYMENT_METHODS = [ - CREDIT_CARD, - BLOCKCHAIN, - BANK_TRANSFER, - CASH, - ] + PAYMENT_METHODS = [CREDIT_CARD, BLOCKCHAIN, BANK_TRANSFER, CASH] PAYMENT_METHOD_CHOICES = [ - (CREDIT_CARD, 'Credit card'), - (BLOCKCHAIN, 'Blockchain'), - (BANK_TRANSFER, 'Bank transfer'), - (CASH, 'Cash'), + (CREDIT_CARD, "Credit card"), + (BLOCKCHAIN, "Blockchain"), + (BANK_TRANSFER, "Bank transfer"), + (CASH, "Cash"), ] payment_method = models.CharField( - max_length=50, - choices=PAYMENT_METHOD_CHOICES, - default='', - blank=True + max_length=50, choices=PAYMENT_METHOD_CHOICES, default="", blank=True ) cancelled = models.BooleanField(default=False) refunded = models.BooleanField( - verbose_name=_('Refunded?'), - help_text=_('Whether this order has been refunded.'), + verbose_name=_("Refunded?"), + help_text=_("Whether this order has been refunded."), default=False, ) customer_comment = models.TextField( - verbose_name=_('Customer comment'), - help_text=_('If you have any comments about the order please enter them here.'), - default='', + verbose_name=_("Customer comment"), + help_text=_("If you have any comments about the order please enter them here."), + default="", blank=True, ) invoice_address = models.TextField( - help_text=_('The invoice address for this order. Leave blank to use the email associated with the logged in user.'), - blank=True + help_text=_( + "The invoice address for this order. Leave blank to use the email associated with the logged in user." + ), + blank=True, ) notes = models.TextField( - help_text='Any internal notes about this order can be entered here. They will not be printed on the invoice or shown to the customer in any way.', - default='', + help_text="Any internal notes about this order can be entered here. They will not be printed on the invoice or shown to the customer in any way.", + default="", blank=True, ) objects = OrderQuerySet.as_manager() def __str__(self): - return 'shop order id #%s' % self.pk + return "shop order id #%s" % self.pk def get_number_of_items(self): - return self.products.aggregate( - sum=Sum('orderproductrelation__quantity') - )['sum'] + return self.products.aggregate(sum=Sum("orderproductrelation__quantity"))["sum"] @property def vat(self): - return Decimal(self.total*Decimal(0.2)) + return Decimal(self.total * Decimal(0.2)) @property def total(self): if self.products.all(): - return Decimal(self.products.aggregate( - sum=Sum( - models.F('orderproductrelation__product__price') * - models.F('orderproductrelation__quantity'), - output_field=models.IntegerField() - ) - )['sum']) + return Decimal( + self.products.aggregate( + sum=Sum( + models.F("orderproductrelation__product__price") + * models.F("orderproductrelation__quantity"), + output_field=models.IntegerField(), + ) + )["sum"] + ) else: return False def get_coinify_callback_url(self, request): """ Check settings for an alternative COINIFY_CALLBACK_HOSTNAME otherwise use the one from the request """ - if hasattr(settings, 'COINIFY_CALLBACK_HOSTNAME') and settings.COINIFY_CALLBACK_HOSTNAME: + if ( + hasattr(settings, "COINIFY_CALLBACK_HOSTNAME") + and settings.COINIFY_CALLBACK_HOSTNAME + ): host = settings.COINIFY_CALLBACK_HOSTNAME else: host = request.get_host() - return 'https://' + host + str(reverse_lazy('shop:coinify_callback', kwargs={'pk': self.pk})) + return ( + "https://" + + host + + str(reverse_lazy("shop:coinify_callback", kwargs={"pk": self.pk})) + ) def get_coinify_thanks_url(self, request): - return 'https://' + request.get_host() + str(reverse_lazy('shop:coinify_thanks', kwargs={'pk': self.pk})) + return ( + "https://" + + request.get_host() + + str(reverse_lazy("shop:coinify_thanks", kwargs={"pk": self.pk})) + ) def get_epay_accept_url(self, request): - return 'https://' + request.get_host() + str(reverse_lazy('shop:epay_thanks', kwargs={'pk': self.pk})) + return ( + "https://" + + request.get_host() + + str(reverse_lazy("shop:epay_thanks", kwargs={"pk": self.pk})) + ) def get_cancel_url(self, request): - return 'https://' + request.get_host() + str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk})) + return ( + "https://" + + request.get_host() + + str(reverse_lazy("shop:order_detail", kwargs={"pk": self.pk})) + ) def get_epay_callback_url(self, request): - return 'https://' + request.get_host() + str(reverse_lazy('shop:epay_callback', kwargs={'pk': self.pk})) + return ( + "https://" + + request.get_host() + + str(reverse_lazy("shop:epay_callback", kwargs={"pk": self.pk})) + ) @property def description(self): return "Order #%s" % self.pk def get_absolute_url(self): - return str(reverse_lazy('shop:order_detail', kwargs={'pk': self.pk})) + return str(reverse_lazy("shop:order_detail", kwargs={"pk": self.pk})) def create_tickets(self, request=None): for order_product in self.orderproductrelation_set.all(): @@ -203,17 +214,24 @@ class Order(CreatedUpdatedModel): ticket_type=order_product.product.ticket_type, ) - already_created_tickets = self.shoptickets.filter(**query_kwargs).count() - tickets_to_create = max(0, order_product.quantity - already_created_tickets) + already_created_tickets = self.shoptickets.filter( + **query_kwargs + ).count() + tickets_to_create = max( + 0, order_product.quantity - already_created_tickets + ) # create the number of tickets required if tickets_to_create > 0: - for _ in range(0, (order_product.quantity - already_created_tickets)): - self.shoptickets.create( - **query_kwargs - ) + for _ in range( + 0, (order_product.quantity - already_created_tickets) + ): + self.shoptickets.create(**query_kwargs) - msg = "Created %s tickets of type: %s" % (order_product.quantity, order_product.product.ticket_type.name) + msg = "Created %s tickets of type: %s" % ( + order_product.quantity, + order_product.product.ticket_type.name, + ) if request: messages.success(request, msg) else: @@ -240,7 +258,10 @@ class Order(CreatedUpdatedModel): self.refunded = True # delete any tickets related to this order if self.shoptickets.all(): - msg = "Order %s marked as refunded, deleting %s tickets..." % (self.pk, self.shoptickets.count()) + msg = "Order %s marked as refunded, deleting %s tickets..." % ( + self.pk, + self.shoptickets.count(), + ) if request: messages.success(request, msg) else: @@ -273,7 +294,10 @@ class Order(CreatedUpdatedModel): return False def is_partially_handed_out(self): - if self.orderproductrelation_set.filter(handed_out=True).count() != 0 and self.orderproductrelation_set.filter(handed_out=False).count() != 0: + if ( + self.orderproductrelation_set.filter(handed_out=True).count() != 0 + and self.orderproductrelation_set.filter(handed_out=False).count() != 0 + ): # some products are handed out, others are not return True else: @@ -313,8 +337,8 @@ class Order(CreatedUpdatedModel): class ProductCategory(CreatedUpdatedModel, UUIDModel): class Meta: - verbose_name = 'Product category' - verbose_name_plural = 'Product categories' + verbose_name = "Product category" + verbose_name_plural = "Product categories" name = models.CharField(max_length=150) slug = models.SlugField() @@ -330,61 +354,51 @@ class ProductCategory(CreatedUpdatedModel, UUIDModel): class Product(CreatedUpdatedModel, UUIDModel): class Meta: - verbose_name = 'Product' - verbose_name_plural = 'Products' - ordering = ['available_in', 'price', 'name'] + verbose_name = "Product" + verbose_name_plural = "Products" + ordering = ["available_in", "price", "name"] category = models.ForeignKey( - 'shop.ProductCategory', - related_name='products', - on_delete=models.PROTECT, + "shop.ProductCategory", related_name="products", on_delete=models.PROTECT ) name = models.CharField(max_length=150) slug = models.SlugField(unique=True, max_length=100) price = models.IntegerField( - help_text=_('Price of the product (in DKK, including VAT).') + help_text=_("Price of the product (in DKK, including VAT).") ) description = models.TextField() available_in = DateTimeRangeField( help_text=_( - 'Which period is this product available for purchase? | ' - '(Format: YYYY-MM-DD HH:MM) | Only one of start/end is required' + "Which period is this product available for purchase? | " + "(Format: YYYY-MM-DD HH:MM) | Only one of start/end is required" ) ) ticket_type = models.ForeignKey( - 'tickets.TicketType', - on_delete=models.PROTECT, - null=True, - blank=True + "tickets.TicketType", on_delete=models.PROTECT, null=True, blank=True ) stock_amount = models.IntegerField( help_text=( - 'Initial amount available in stock if there is a limited ' - 'supply, e.g. fridge space' + "Initial amount available in stock if there is a limited " + "supply, e.g. fridge space" ), null=True, - blank=True + blank=True, ) objects = ProductQuerySet.as_manager() def __str__(self): - return '{} ({} DKK)'.format( - self.name, - self.price, - ) + return "{} ({} DKK)".format(self.name, self.price) def clean(self): - if self.category.name == 'Tickets' and not self.ticket_type: - raise ValidationError( - 'Products with category Tickets need a ticket_type' - ) + if self.category.name == "Tickets" and not self.ticket_type: + raise ValidationError("Products with category Tickets need a ticket_type") def is_available(self): """ Is the product available or not? @@ -407,7 +421,7 @@ class Product(CreatedUpdatedModel, UUIDModel): def is_old(self): now = timezone.now() - if hasattr(self.available_in, 'upper') and self.available_in.upper: + if hasattr(self.available_in, "upper") and self.available_in.upper: return self.available_in.upper < now return False @@ -425,10 +439,8 @@ class Product(CreatedUpdatedModel, UUIDModel): # or is marked to be paid with cash or bank transfer, meaning it is a # "reservation" of the product in question. sold = OrderProductRelation.objects.filter( - product=self, - order__open=None, - order__cancelled=False, - ).aggregate(Sum('quantity'))['quantity__sum'] + product=self, order__open=None, order__cancelled=False + ).aggregate(Sum("quantity"))["quantity__sum"] total_left = self.stock_amount - (sold or 0) @@ -445,8 +457,8 @@ class Product(CreatedUpdatedModel, UUIDModel): class OrderProductRelation(CreatedUpdatedModel): - order = models.ForeignKey('shop.Order', on_delete=models.PROTECT) - product = models.ForeignKey('shop.Product', on_delete=models.PROTECT) + order = models.ForeignKey("shop.Order", on_delete=models.PROTECT) + product = models.ForeignKey("shop.Product", on_delete=models.PROTECT) quantity = models.PositiveIntegerField() handed_out = models.BooleanField(default=False) @@ -457,76 +469,64 @@ class OrderProductRelation(CreatedUpdatedModel): def clean(self): if self.handed_out and not self.order.paid: raise ValidationError( - 'Product can not be handed out when order is not paid.' + "Product can not be handed out when order is not paid." ) class EpayCallback(CreatedUpdatedModel, UUIDModel): class Meta: - verbose_name = 'Epay Callback' - verbose_name_plural = 'Epay Callbacks' - ordering = ['-created'] + verbose_name = "Epay Callback" + verbose_name_plural = "Epay Callbacks" + ordering = ["-created"] payload = JSONField() md5valid = models.BooleanField(default=False) def __str__(self): - return 'callback at %s (md5 valid: %s)' % (self.created, self.md5valid) + return "callback at %s (md5 valid: %s)" % (self.created, self.md5valid) class EpayPayment(CreatedUpdatedModel, UUIDModel): class Meta: - verbose_name = 'Epay Payment' - verbose_name_plural = 'Epay Payments' + verbose_name = "Epay Payment" + verbose_name_plural = "Epay Payments" - order = models.OneToOneField('shop.Order', on_delete=models.PROTECT) - callback = models.ForeignKey('shop.EpayCallback', on_delete=models.PROTECT) + order = models.OneToOneField("shop.Order", on_delete=models.PROTECT) + callback = models.ForeignKey("shop.EpayCallback", on_delete=models.PROTECT) txnid = models.IntegerField() class CreditNote(CreatedUpdatedModel): class Meta: - ordering = ['-created'] + ordering = ["-created"] - amount = models.DecimalField( - max_digits=10, - decimal_places=2 - ) + amount = models.DecimalField(max_digits=10, decimal_places=2) - text = models.TextField( - help_text="Description of what this credit note covers" - ) + text = models.TextField(help_text="Description of what this credit note covers") - pdf = models.FileField( - null=True, - blank=True, - upload_to='creditnotes/' - ) + pdf = models.FileField(null=True, blank=True, upload_to="creditnotes/") user = models.ForeignKey( - 'auth.User', - verbose_name=_('User'), - help_text=_('The user this credit note belongs to, if any.'), - related_name='creditnotes', + "auth.User", + verbose_name=_("User"), + help_text=_("The user this credit note belongs to, if any."), + related_name="creditnotes", on_delete=models.PROTECT, null=True, - blank=True + blank=True, ) customer = models.TextField( - help_text="Customer info if no user is selected", - blank=True, - default='', + help_text="Customer info if no user is selected", blank=True, default="" ) - danish_vat = models.BooleanField( - help_text="Danish VAT?", - default=True - ) + danish_vat = models.BooleanField(help_text="Danish VAT?", default=True) paid = models.BooleanField( - verbose_name=_('Paid?'), - help_text=_('Whether the amount in this creditnote has been paid back to the customer.'), + verbose_name=_("Paid?"), + help_text=_( + "Whether the amount in this creditnote has been paid back to the customer." + ), default=False, ) @@ -536,24 +536,24 @@ class CreditNote(CreatedUpdatedModel): errors = [] if self.user and self.customer: msg = "Customer info should be blank if a user is selected." - errors.append(ValidationError({'user', msg})) - errors.append(ValidationError({'customer', msg})) + errors.append(ValidationError({"user", msg})) + errors.append(ValidationError({"customer", msg})) if not self.user and not self.customer: msg = "Either pick a user or fill in Customer info" - errors.append(ValidationError({'user', msg})) - errors.append(ValidationError({'customer', msg})) + errors.append(ValidationError({"user", msg})) + errors.append(ValidationError({"customer", msg})) if errors: raise ValidationError(errors) def __str__(self): if self.user: - return 'creditnoote#%s - %s DKK (customer: user %s)' % ( + return "creditnoote#%s - %s DKK (customer: user %s)" % ( self.id, self.amount, self.user.email, ) else: - return 'creditnoote#%s - %s DKK (customer: %s)' % ( + return "creditnoote#%s - %s DKK (customer: %s)" % ( self.id, self.amount, self.customer, @@ -562,34 +562,28 @@ class CreditNote(CreatedUpdatedModel): @property def vat(self): if self.danish_vat: - return Decimal(round(self.amount*Decimal(0.2), 2)) + return Decimal(round(self.amount * Decimal(0.2), 2)) else: return 0 @property def filename(self): - return 'bornhack_creditnote_%s.pdf' % self.pk + return "bornhack_creditnote_%s.pdf" % self.pk class Invoice(CreatedUpdatedModel): order = models.OneToOneField( - 'shop.Order', - null=True, - blank=True, - on_delete=models.PROTECT + "shop.Order", null=True, blank=True, on_delete=models.PROTECT ) customorder = models.OneToOneField( - 'shop.CustomOrder', - null=True, - blank=True, - on_delete=models.PROTECT + "shop.CustomOrder", null=True, blank=True, on_delete=models.PROTECT ) - pdf = models.FileField(null=True, blank=True, upload_to='invoices/') + pdf = models.FileField(null=True, blank=True, upload_to="invoices/") sent_to_customer = models.BooleanField(default=False) def __str__(self): if self.order: - return 'invoice#%s - shop order %s - %s - total %s DKK (sent to %s: %s)' % ( + return "invoice#%s - shop order %s - %s - total %s DKK (sent to %s: %s)" % ( self.id, self.order.id, self.order.created, @@ -598,52 +592,60 @@ class Invoice(CreatedUpdatedModel): self.sent_to_customer, ) elif self.customorder: - return 'invoice#%s - custom order %s - %s - amount %s DKK (customer: %s)' % ( - self.id, - self.customorder.id, - self.customorder.created, - self.customorder.amount, - unidecode(self.customorder.customer), + return ( + "invoice#%s - custom order %s - %s - amount %s DKK (customer: %s)" + % ( + self.id, + self.customorder.id, + self.customorder.created, + self.customorder.amount, + unidecode(self.customorder.customer), + ) ) @property def filename(self): - return 'bornhack_invoice_%s.pdf' % self.pk + return "bornhack_invoice_%s.pdf" % self.pk def regretdate(self): - return self.created+timedelta(days=15) + return self.created + timedelta(days=15) class CoinifyAPIInvoice(CreatedUpdatedModel): coinify_id = models.IntegerField(null=True) invoicejson = JSONField() - order = models.ForeignKey('shop.Order', related_name="coinify_api_invoices", on_delete=models.PROTECT) + order = models.ForeignKey( + "shop.Order", related_name="coinify_api_invoices", on_delete=models.PROTECT + ) def __str__(self): return "coinifyinvoice for order #%s" % self.order.id @property def expired(self): - return parse_datetime(self.invoicejson['expire_time']) < timezone.now() + return parse_datetime(self.invoicejson["expire_time"]) < timezone.now() class CoinifyAPICallback(CreatedUpdatedModel): headers = JSONField() payload = JSONField(blank=True) - body = models.TextField(default='') - order = models.ForeignKey('shop.Order', related_name="coinify_api_callbacks", on_delete=models.PROTECT) + body = models.TextField(default="") + order = models.ForeignKey( + "shop.Order", related_name="coinify_api_callbacks", on_delete=models.PROTECT + ) authenticated = models.BooleanField(default=False) def __str__(self): - return 'order #%s callback at %s' % (self.order.id, self.created) + return "order #%s callback at %s" % (self.order.id, self.created) class CoinifyAPIRequest(CreatedUpdatedModel): - order = models.ForeignKey('shop.Order', related_name="coinify_api_requests", on_delete=models.PROTECT) + order = models.ForeignKey( + "shop.Order", related_name="coinify_api_requests", on_delete=models.PROTECT + ) method = models.CharField(max_length=100) payload = JSONField() response = JSONField() def __str__(self): - return 'order %s api request %s' % (self.order.id, self.method) - + return "order %s api request %s" % (self.order.id, self.method)