diff --git a/oscar_promotions/admin.py b/oscar_promotions/admin.py index 8c74346..df6af08 100644 --- a/oscar_promotions/admin.py +++ b/oscar_promotions/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from oscar.core.loading import get_model + AutomaticProductList = get_model('oscar_promotions', 'AutomaticProductList') HandPickedProductList = get_model('oscar_promotions', 'HandPickedProductList') Image = get_model('oscar_promotions', 'Image') @@ -11,6 +12,7 @@ RawHTML = get_model('oscar_promotions', 'RawHTML') SingleProduct = get_model('oscar_promotions', 'SingleProduct') TabbedBlock = get_model('oscar_promotions', 'TabbedBlock') +TimeBasedPromotion = get_model('oscar_promotions', 'TimeBasedPromotion') class OrderProductInline(admin.TabularInline): @@ -31,6 +33,26 @@ class KeywordPromotionAdmin(admin.ModelAdmin): readonly_fields = ['clicks'] +class TimeBasedPromotionOptions(admin.ModelAdmin): + list_display = ('title', 'available_since_date', 'available_since_time', 'available_until_date', + 'available_until_time', 'enabled') + list_filter = ('enabled',) + + actions = ['enable', 'disable'] + + def disable(self, request, queryset): + rows_updated = queryset.update(enabled=False) + self.message_user(request, "%s desactivadas exitosamente." % rows_updated) + + disable.short_description = "Desactivar las promociones seleccionadas" + + def enable(self, request, queryset): + rows_updated = queryset.update(enabled=True) + self.message_user(request, "%s activadas exitosamente." % rows_updated) + + enable.short_description = "Habilitar las promociones seleccionadas" + + admin.site.register(Image) admin.site.register(MultiImage) admin.site.register(RawHTML) @@ -40,3 +62,4 @@ class KeywordPromotionAdmin(admin.ModelAdmin): admin.site.register(PagePromotion, PagePromotionAdmin) admin.site.register(KeywordPromotion, KeywordPromotionAdmin) admin.site.register(SingleProduct) +admin.site.register(TimeBasedPromotion, TimeBasedPromotionOptions) diff --git a/oscar_promotions/apps.py b/oscar_promotions/apps.py index 2cdd59b..5cc9f77 100644 --- a/oscar_promotions/apps.py +++ b/oscar_promotions/apps.py @@ -14,7 +14,6 @@ class PromotionsConfig(OscarConfig): def ready(self): super().ready() - self.home_view = get_class('oscar_promotions.views', 'HomeView', module_prefix='oscar_promotions') self.record_click_view = get_class( 'oscar_promotions.views', 'RecordClickView', module_prefix='oscar_promotions' ) @@ -33,6 +32,5 @@ def get_urls(self): self.record_click_view.as_view(model=KeywordPromotion), name='keyword-click', ), - url(r'^$', self.home_view.as_view(), name='home'), ] return self.post_process_urls(urls) diff --git a/oscar_promotions/dashboard/apps.py b/oscar_promotions/dashboard/apps.py index ea30118..2a7c9e9 100644 --- a/oscar_promotions/dashboard/apps.py +++ b/oscar_promotions/dashboard/apps.py @@ -10,7 +10,7 @@ class PromotionsDashboardConfig(OscarDashboardConfig): name = 'oscar_promotions.dashboard' namespace = 'oscar_promotions_dashboard' verbose_name = _("Promotions dashboard") - default_permissions = ['is_staff'] + default_permissions = ['is_staff', 'partner.dashboard_access'] # Dynamically set the CRUD views for all promotion classes view_names = ( @@ -55,7 +55,7 @@ def get_urls(self): url(r'^$', self.list_view.as_view(), name='promotion-list'), url(r'^pages/$', self.page_list.as_view(), name='promotion-list-by-page'), url( - r'^page/-(?P/([\w-]+(/[\w-]+)*/)?)$', + r'^page/-(?P/(([\w-]|\s)+(/([\w-]|\s)+)*/)?)$', self.page_detail.as_view(), name='promotion-list-by-url', ), diff --git a/oscar_promotions/dashboard/forms.py b/oscar_promotions/dashboard/forms.py index cbd05d9..e1b9a29 100644 --- a/oscar_promotions/dashboard/forms.py +++ b/oscar_promotions/dashboard/forms.py @@ -1,5 +1,7 @@ from django import forms +from django.contrib.sites.models import Site from django.utils.translation import gettext_lazy as _ +from django.forms import SelectMultiple from oscar.core.loading import get_class, get_model from oscar.forms.fields import ExtendedURLField @@ -26,7 +28,7 @@ class PromotionTypeSelectForm(forms.Form): class RawHTMLForm(forms.ModelForm): class Meta: model = RawHTML - fields = ['name', 'body'] + fields = ['name', 'body', 'site'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -36,14 +38,14 @@ def __init__(self, *args, **kwargs): class SingleProductForm(forms.ModelForm): class Meta: model = SingleProduct - fields = ['name', 'product', 'description'] + fields = ['name', 'product', 'description', 'site'] widgets = {'product': ProductSelect} class HandPickedProductListForm(forms.ModelForm): class Meta: model = HandPickedProductList - fields = ['name', 'description', 'link_url', 'link_text'] + fields = ['name', 'description', 'link_url', 'link_text', 'site'] class OrderedProductForm(forms.ModelForm): @@ -79,3 +81,12 @@ def clean_page_url(self): page_url += '/' return page_url + + +class PromotionsSearchForm(forms.Form): + + sites_choices = (('', '---------'),) + tuple([(k, v) for k, v in Site.objects.all().values_list("id", "name")]) + sites = forms.MultipleChoiceField( + choices=sites_choices, label=_("Site"), required=False, + widget=SelectMultiple(attrs={'data-multiple': 'true'}) + ) diff --git a/oscar_promotions/dashboard/views.py b/oscar_promotions/dashboard/views.py index b3861a1..a372b8a 100644 --- a/oscar_promotions/dashboard/views.py +++ b/oscar_promotions/dashboard/views.py @@ -12,6 +12,7 @@ from oscar_promotions import app_settings from oscar_promotions.conf import PROMOTION_CLASSES +from oscar_promotions.dashboard.forms import PromotionsSearchForm AutomaticProductList = get_model('oscar_promotions', 'AutomaticProductList') HandPickedProductList = get_model('oscar_promotions', 'HandPickedProductList') @@ -35,6 +36,8 @@ class ListView(generic.TemplateView): + form = None + form_class = PromotionsSearchForm template_name = 'oscar_promotions/dashboard/promotion_list.html' def get_context_data(self): @@ -44,6 +47,7 @@ def get_context_data(self): num_promotions = 0 for klass in PROMOTION_CLASSES: objects = klass.objects.all() + objects = self.apply_search(objects) num_promotions += objects.count() data.append(objects) promotions = itertools.chain(*data) @@ -51,9 +55,30 @@ def get_context_data(self): 'num_promotions': num_promotions, 'promotions': promotions, 'select_form': SelectForm(), + 'form': self.form } return ctx + def apply_search(self, queryset): + """ + Search through the filtered queryset. + + We must make sure that we don't return search results that the user is not allowed + to see (see filter_queryset). + """ + self.form = self.form_class(self.request.GET) + + if not self.form.is_valid(): + return queryset + + data = self.form.cleaned_data + + site_list = data.get("sites", []) + if len(site_list) > 0: + queryset = queryset.filter(site_id__in=site_list) + + return queryset + class CreateRedirectView(generic.RedirectView): permanent = True @@ -171,18 +196,18 @@ class CreateSingleProductView(CreateView): class CreateImageView(CreateView): model = Image - fields = ['name', 'link_url', 'image'] + fields = ['name', 'link_url', 'image', 'site'] class CreateMultiImageView(CreateView): model = MultiImage - fields = ['name'] + fields = ['name', 'site'] class CreateAutomaticProductListView(CreateView): model = AutomaticProductList fields = ['name', 'description', 'link_url', 'link_text', 'method', - 'num_products'] + 'num_products', 'site'] class CreateHandPickedProductListView(CreateView): @@ -290,12 +315,12 @@ class UpdateSingleProductView(UpdateView): class UpdateImageView(UpdateView): model = Image - fields = ['name', 'link_url', 'image'] + fields = ['name', 'link_url', 'image', 'site'] class UpdateMultiImageView(UpdateView): model = MultiImage - fields = ['name', 'images'] + fields = ['name', 'images', 'site'] class UpdateAutomaticProductListView(UpdateView): diff --git a/oscar_promotions/migrations/0002_auto_20150604_1450.py b/oscar_promotions/migrations/0002_auto_20150604_1450.py new file mode 100644 index 0000000..fecb1ec --- /dev/null +++ b/oscar_promotions/migrations/0002_auto_20150604_1450.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='handpickedproductlist', + name='products', + field=models.ManyToManyField(to='catalogue.Product', verbose_name='Products', through='oscar_promotions.OrderedProduct', blank=True), + ), + migrations.AlterField( + model_name='multiimage', + name='images', + field=models.ManyToManyField(help_text='Choose the Image content blocks that this block will use. (You may need to create some first).', to='oscar_promotions.Image', blank=True), + ), + ] diff --git a/oscar_promotions/migrations/0003_timebasedpromotion.py b/oscar_promotions/migrations/0003_timebasedpromotion.py new file mode 100644 index 0000000..a3b5822 --- /dev/null +++ b/oscar_promotions/migrations/0003_timebasedpromotion.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-02-07 15:45 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0002_auto_20150604_1450'), + ] + + operations = [ + migrations.CreateModel( + name='TimeBasedPromotion', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=50)), + ('description', models.CharField(max_length=100)), + ('available_since_date', models.DateField(blank=True, null=True)), + ('available_since_time', models.TimeField()), + ('available_until_date', models.DateField(blank=True, null=True)), + ('available_until_time', models.TimeField()), + ('promotional_code', models.CharField(max_length=50, blank=True, null=True)), + ('link', models.CharField(blank=True, max_length=200, null=True)), + ], + ), + ] diff --git a/oscar_promotions/migrations/0004_timebasedpromotion_enabled.py b/oscar_promotions/migrations/0004_timebasedpromotion_enabled.py new file mode 100644 index 0000000..e7a58b0 --- /dev/null +++ b/oscar_promotions/migrations/0004_timebasedpromotion_enabled.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-03-07 19:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0003_timebasedpromotion'), + ] + + operations = [ + migrations.AddField( + model_name='timebasedpromotion', + name='enabled', + field=models.BooleanField(default=True), + ), + ] diff --git a/oscar_promotions/migrations/0005_timebasedpromotion_call_to_action.py b/oscar_promotions/migrations/0005_timebasedpromotion_call_to_action.py new file mode 100644 index 0000000..ef11172 --- /dev/null +++ b/oscar_promotions/migrations/0005_timebasedpromotion_call_to_action.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-06-27 03:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0004_timebasedpromotion_enabled'), + ] + + operations = [ + migrations.AddField( + model_name='timebasedpromotion', + name='call_to_action', + field=models.CharField(blank=True, max_length=200, null=True), + ), + ] diff --git a/oscar_promotions/migrations/0006_auto_20180706_1322.py b/oscar_promotions/migrations/0006_auto_20180706_1322.py new file mode 100644 index 0000000..0a7bc48 --- /dev/null +++ b/oscar_promotions/migrations/0006_auto_20180706_1322.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2018-07-06 17:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0005_timebasedpromotion_call_to_action'), + ] + + operations = [ + migrations.AlterField( + model_name='timebasedpromotion', + name='promotional_code', + field=models.CharField(blank=True, max_length=50, null=True), + ), + ] diff --git a/oscar_promotions/migrations/0007_imageextrainfo.py b/oscar_promotions/migrations/0007_imageextrainfo.py new file mode 100644 index 0000000..f4eac67 --- /dev/null +++ b/oscar_promotions/migrations/0007_imageextrainfo.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.14 on 2018-12-14 15:14 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0006_auto_20180706_1322'), + ] + + operations = [ + migrations.CreateModel( + name='ImageExtraInfo', + fields=[ + ('description', models.TextField(verbose_name='Description for mobile view')), + ('image', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='extra_info', serialize=False, to='oscar_promotions.Image')), + ], + options={ + 'verbose_name': 'ImageExtraInfo', + 'verbose_name_plural': 'ImageExtraInfo', + }, + ), + ] diff --git a/oscar_promotions/migrations/0008_auto_20190802_1149.py b/oscar_promotions/migrations/0008_auto_20190802_1149.py new file mode 100644 index 0000000..f98256c --- /dev/null +++ b/oscar_promotions/migrations/0008_auto_20190802_1149.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.17 on 2019-08-02 15:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0007_imageextrainfo'), + ] + + operations = [ + migrations.RemoveField( + model_name='imageextrainfo', + name='image', + ), + migrations.AddField( + model_name='timebasedpromotion', + name='icon_name', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.DeleteModel( + name='ImageExtraInfo', + ), + ] diff --git a/oscar_promotions/migrations/0009_timebasedpromotion_site.py b/oscar_promotions/migrations/0009_timebasedpromotion_site.py new file mode 100644 index 0000000..e270094 --- /dev/null +++ b/oscar_promotions/migrations/0009_timebasedpromotion_site.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.12 on 2020-07-02 18:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ('oscar_promotions', '0008_auto_20190802_1149'), + ] + + operations = [ + migrations.AddField( + model_name='timebasedpromotion', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + ] diff --git a/oscar_promotions/migrations/0010_auto_20200706_1259.py b/oscar_promotions/migrations/0010_auto_20200706_1259.py new file mode 100644 index 0000000..c9673e1 --- /dev/null +++ b/oscar_promotions/migrations/0010_auto_20200706_1259.py @@ -0,0 +1,60 @@ +# Generated by Django 2.2.12 on 2020-07-06 16:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ('oscar_promotions', '0009_timebasedpromotion_site'), + ] + + operations = [ + migrations.AddField( + model_name='automaticproductlist', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='handpickedproductlist', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='image', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='keywordpromotion', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='multiimage', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='pagepromotion', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='rawhtml', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='singleproduct', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + migrations.AddField( + model_name='tabbedblock', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + ] diff --git a/oscar_promotions/migrations/0011_auto_20200810_1304.py b/oscar_promotions/migrations/0011_auto_20200810_1304.py new file mode 100644 index 0000000..c35ec12 --- /dev/null +++ b/oscar_promotions/migrations/0011_auto_20200810_1304.py @@ -0,0 +1,49 @@ +# Generated by Django 2.2.14 on 2020-08-10 18:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('oscar_promotions', '0010_auto_20200706_1259'), + ] + + operations = [ + migrations.AlterField( + model_name='automaticproductlist', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site', verbose_name='Site'), + ), + migrations.AlterField( + model_name='handpickedproductlist', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site', verbose_name='Site'), + ), + migrations.AlterField( + model_name='image', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site', verbose_name='Site'), + ), + migrations.AlterField( + model_name='multiimage', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site', verbose_name='Site'), + ), + migrations.AlterField( + model_name='rawhtml', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site', verbose_name='Site'), + ), + migrations.AlterField( + model_name='singleproduct', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site', verbose_name='Site'), + ), + migrations.AlterField( + model_name='tabbedblock', + name='site', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='sites.Site', verbose_name='Site'), + ), + ] diff --git a/oscar_promotions/models.py b/oscar_promotions/models.py index d2a37b5..3a723ec 100644 --- a/oscar_promotions/models.py +++ b/oscar_promotions/models.py @@ -1,7 +1,11 @@ +import datetime + +import pytz from django.contrib.contenttypes import fields from django.contrib.contenttypes.models import ContentType from django.db import models from django.urls import reverse +from django.utils.timezone import now from django.utils.translation import gettext_lazy as _, pgettext_lazy from oscar.core.loading import get_model from oscar.models.fields import ExtendedURLField @@ -23,6 +27,7 @@ class LinkedPromotion(models.Model): display_order = models.PositiveIntegerField(_("Display Order"), default=0) clicks = models.PositiveIntegerField(_("Clicks"), default=0) date_created = models.DateTimeField(_("Date Created"), auto_now_add=True) + site = models.ForeignKey('sites.Site', on_delete=models.CASCADE, null=False, blank=False, default=1) class Meta: abstract = True @@ -91,6 +96,9 @@ class AbstractPromotion(models.Model): verbose_name=_('Keywords')) pages = fields.GenericRelation(PagePromotion, verbose_name=_('Pages')) + site = models.ForeignKey('sites.Site', on_delete=models.CASCADE, null=False, blank=False, default=1, + verbose_name=_('Site')) + class Meta: abstract = True app_label = 'oscar_promotions' @@ -140,6 +148,7 @@ class RawHTML(AbstractPromotion): _type = 'Raw HTML' name = models.CharField(_("Name"), max_length=128) + # Used to determine how to render the promotion (eg # if a different width container is required). This isn't always # required. @@ -150,7 +159,7 @@ class RawHTML(AbstractPromotion): body = models.TextField(_("HTML")) date_created = models.DateTimeField(auto_now_add=True) - class Meta: + class Meta(AbstractPromotion.Meta): verbose_name = _('Raw HTML') verbose_name_plural = _('Raw HTML') @@ -167,6 +176,7 @@ class Image(AbstractPromotion): """ _type = 'Image' name = models.CharField(_("Name"), max_length=128) + cta_text = models.TextField(_('CTA Text'), blank=True, help_text=_('Call to action text')) link_url = ExtendedURLField( _('Link URL'), blank=True, help_text=_('This is where this promotion links to')) @@ -178,7 +188,7 @@ class Image(AbstractPromotion): def __str__(self): return self.name - class Meta: + class Meta(AbstractPromotion.Meta): verbose_name = _("Image") verbose_name_plural = _("Image") @@ -201,7 +211,7 @@ class MultiImage(AbstractPromotion): def __str__(self): return self.name - class Meta: + class Meta(AbstractPromotion.Meta): verbose_name = _("Multi Image") verbose_name_plural = _("Multi Images") @@ -219,7 +229,7 @@ def __str__(self): def template_context(self, request): return {'product': self.product} - class Meta: + class Meta(AbstractPromotion.Meta): verbose_name = _("Single product") verbose_name_plural = _("Single product") @@ -237,7 +247,7 @@ class AbstractProductList(AbstractPromotion): link_text = models.CharField(_("Link text"), max_length=255, blank=True) date_created = models.DateTimeField(auto_now_add=True) - class Meta: + class Meta(AbstractPromotion.Meta): abstract = True verbose_name = _("Product list") verbose_name_plural = _("Product lists") @@ -266,7 +276,7 @@ def get_queryset(self): def get_products(self): return self.get_queryset() - class Meta: + class Meta(AbstractProductList.Meta): verbose_name = _("Hand Picked Product List") verbose_name_plural = _("Hand Picked Product Lists") @@ -314,7 +324,7 @@ def get_queryset(self): def get_products(self): return self.get_queryset()[:self.num_products] - class Meta: + class Meta(AbstractProductList.Meta): verbose_name = _("Automatic product list") verbose_name_plural = _("Automatic product lists") @@ -327,7 +337,7 @@ class OrderedProductList(HandPickedProductList): verbose_name=_("Tabbed Block")) display_order = models.PositiveIntegerField(_('Display Order'), default=0) - class Meta: + class Meta(HandPickedProductList.Meta): ordering = ('display_order',) verbose_name = _("Ordered Product List") verbose_name_plural = _("Ordered Product Lists") @@ -340,6 +350,46 @@ class TabbedBlock(AbstractPromotion): pgettext_lazy("Tabbed block title", "Title"), max_length=255) date_created = models.DateTimeField(_("Date Created"), auto_now_add=True) - class Meta: + class Meta(AbstractPromotion.Meta): verbose_name = _("Tabbed Block") verbose_name_plural = _("Tabbed Blocks") + + +class TimeBasedPromotion(models.Model): + title = models.CharField(max_length=50) + description = models.CharField(max_length=100) + available_since_date = models.DateField(blank=True, null=True) + available_since_time = models.TimeField() + available_until_date = models.DateField(blank=True, null=True) + available_until_time = models.TimeField() + promotional_code = models.CharField(max_length=50, blank=True, null=True) + call_to_action = models.CharField(max_length=200, blank=True, null=True) + link = models.CharField(max_length=200, blank=True, null=True) + enabled = models.BooleanField(default=True) + icon_name = models.CharField(max_length=30, null=True, blank=True) + site = models.ForeignKey('sites.Site', on_delete=models.CASCADE, null=False, blank=False, default=1) + + def get_remaining_time(self): + """ + :return: None if no expire time defined. + :return: DateTime instance until which this promo should show. + + """ + until_date = self.available_until_date + until_time = self.available_until_time + + if until_date is None and until_time is None: + return None + + if until_date is None: + current_timezone = pytz.timezone(settings.TIME_ZONE) + current_datetime = now().astimezone(current_timezone) + + until_date = current_datetime.date() + + if until_time is None: + until_time = datetime.time(hour=23, minute=59, second=59) + + until_datetime = datetime.datetime.combine(until_date, until_time) + + return until_datetime diff --git a/oscar_promotions/templatetags/promotion_tags.py b/oscar_promotions/templatetags/promotion_tags.py index 68abf03..15e0f07 100644 --- a/oscar_promotions/templatetags/promotion_tags.py +++ b/oscar_promotions/templatetags/promotion_tags.py @@ -1,5 +1,21 @@ from django.template import Library from django.template.loader import select_template +import logging +import pytz + +from django import template +from django.template.loader import select_template +from django.conf import settings +from django.utils.timezone import now +from django.db.models import Q + +from oscar_promotions.models import TimeBasedPromotion, MultiImage + +from dynamic_preferences.registries import global_preferences_registry + +register = template.Library() +logger = logging.getLogger(__name__) +global_preferences = global_preferences_registry.manager() register = Library() @@ -17,3 +33,47 @@ def render_promotion(context, promotion): ctx.update(**promotion.template_context(request=request)) return template.render(ctx, request) + + +@register.inclusion_tag('oscar_promotions/partials/prices_range.html', takes_context=True) +def search_facets_list(context): + facets_list = settings.OSCAR_SEARCH_FACETS['queries'].items()[0][1]['queries'] + + ctx = dict() + ctx['facets_list'] = facets_list + + return ctx + + +@register.inclusion_tag('oscar_promotions/partials/category_list.html', takes_context=True) +def show_category_list(context): + categories_list = settings.CATEGORIES_LIST + + return {'categories_list': categories_list} + + +@register.simple_tag() +def get_time_promotion(): + current_timezone = pytz.timezone(settings.TIME_ZONE) + current_datetime = now().astimezone(current_timezone) + + tpl = TimeBasedPromotion.objects.filter(enabled=True) + tpl = tpl.filter(Q(available_since_date__lte=current_datetime) | + Q(available_since_date__isnull=True)) + tpl = tpl.filter(Q(available_until_date__gte=current_datetime) | + Q(available_until_date__isnull=True)) + tpl = tpl.filter(available_since_time__lte=current_datetime.time(), + available_until_time__gte=current_datetime.time()) + + return tpl + + +@register.simple_tag() +def get_multi_images_promotion(): + try: + multi_image = MultiImage.objects.get(name=global_preferences['PromotionalOffer__PROMOTIONAL_CAROUSEL']) + except MultiImage.DoesNotExist as err: + logger.exception(err) + else: + return multi_image.images.all() if multi_image else [] + diff --git a/oscar_promotions/views.py b/oscar_promotions/views.py index a716067..513489d 100644 --- a/oscar_promotions/views.py +++ b/oscar_promotions/views.py @@ -25,4 +25,4 @@ def get_redirect_url(self, **kwargs): if prom.promotion.has_link: prom.record_click() return prom.link_url - return reverse('promotions:home') + return reverse('promotions:home') \ No newline at end of file