diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/products/admin.py | 22 | ||||
-rw-r--r-- | app/products/migrations/0001_initial.py | 36 | ||||
-rw-r--r-- | app/products/migrations/0002_auto_20191008_0841.py | 29 | ||||
-rw-r--r-- | app/products/migrations/0003_auto_20191008_0941.py | 30 | ||||
-rw-r--r-- | app/products/migrations/__init__.py | 0 | ||||
-rw-r--r-- | app/products/models.py | 89 | ||||
-rw-r--r-- | app/products/static/product-loader.js | 47 | ||||
-rw-r--r-- | app/products/templates/products/snippet.html | 26 | ||||
-rw-r--r-- | app/products/views.py | 15 |
9 files changed, 294 insertions, 0 deletions
diff --git a/app/products/admin.py b/app/products/admin.py new file mode 100644 index 0000000..d8059f2 --- /dev/null +++ b/app/products/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin +from .models import Brand, Product + + +@admin.register(Product) +class ProductAdmin(admin.ModelAdmin): + list_display = ('name', 'admin_thumbnail', 'rating', 'pub_date') + search_fields = ['name', 'body_markdown'] + list_filter = ('rating', 'pub_date') + + class Media: + js = ('next-prev-links.js',) + + +@admin.register(Brand) +class BrandAdmin(admin.ModelAdmin): + list_display = ('name', ) + search_fields = ['name',] + list_filter = ('pub_date',) + + class Media: + js = ('next-prev-links.js',) diff --git a/app/products/migrations/0001_initial.py b/app/products/migrations/0001_initial.py new file mode 100644 index 0000000..eb2a822 --- /dev/null +++ b/app/products/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 2.2.6 on 2019-10-08 07:46 + +from django.db import migrations, models +import products.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('slug', models.CharField(max_length=50)), + ('pub_date', models.DateTimeField()), + ('body_markdown', models.TextField(blank=True)), + ('body_html', models.TextField(blank=True, null=True)), + ('primary_offer_url', models.CharField(max_length=400)), + ('primary_offer_price', models.IntegerField()), + ('secondary_offer_url', models.CharField(blank=True, max_length=400, null=True)), + ('secondar_offer_price', models.IntegerField(blank=True, null=True)), + ('rating', models.IntegerField()), + ('is_public', models.BooleanField(default=True)), + ('image', models.FileField(blank=True, null=True, upload_to=products.models.get_upload_path)), + ], + options={ + 'ordering': ('-pub_date',), + }, + ), + ] diff --git a/app/products/migrations/0002_auto_20191008_0841.py b/app/products/migrations/0002_auto_20191008_0841.py new file mode 100644 index 0000000..11b2d59 --- /dev/null +++ b/app/products/migrations/0002_auto_20191008_0841.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.6 on 2019-10-08 08:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Brand', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('slug', models.CharField(max_length=50)), + ('pub_date', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.AddField( + model_name='product', + name='brand', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.Brand'), + preserve_default=False, + ), + ] diff --git a/app/products/migrations/0003_auto_20191008_0941.py b/app/products/migrations/0003_auto_20191008_0941.py new file mode 100644 index 0000000..085fd32 --- /dev/null +++ b/app/products/migrations/0003_auto_20191008_0941.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.6 on 2019-10-08 09:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0002_auto_20191008_0841'), + ] + + operations = [ + migrations.RenameField( + model_name='product', + old_name='secondar_offer_price', + new_name='secondary_offer_price', + ), + migrations.AddField( + model_name='product', + name='primary_offer_retailer', + field=models.CharField(default='Amazon', max_length=400), + preserve_default=False, + ), + migrations.AddField( + model_name='product', + name='secondary_offer_retailer', + field=models.CharField(default='Amazon', max_length=400), + preserve_default=False, + ), + ] diff --git a/app/products/migrations/__init__.py b/app/products/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/products/migrations/__init__.py diff --git a/app/products/models.py b/app/products/models.py new file mode 100644 index 0000000..bf33db4 --- /dev/null +++ b/app/products/models.py @@ -0,0 +1,89 @@ +import os +from PIL import Image +from django.db import models +from django.db.models.signals import post_save +from django.contrib.sitemaps import Sitemap +from django.dispatch import receiver +from django.urls import reverse +from django.apps import apps +from django.utils.html import format_html +from django.conf import settings +from django.template.defaultfilters import slugify + +from photos.utils import resize_image +from utils.util import render_images, render_products, parse_video, markdown_to_html + + +def get_upload_path(self, filename): + return "images/products/%s" % (filename) + +class Brand(models.Model): + name = models.CharField(max_length=200) + slug = models.CharField(max_length=50) + pub_date = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + +class Product(models.Model): + name = models.CharField(max_length=200) + brand = models.ForeignKey(Brand, on_delete=models.CASCADE) + slug = models.CharField(max_length=50) + pub_date = models.DateTimeField() + body_markdown = models.TextField(blank=True) + body_html = models.TextField(null=True, blank=True) + primary_offer_retailer = models.CharField(max_length=400) + primary_offer_url = models.CharField(max_length=400) + primary_offer_price = models.IntegerField() + secondary_offer_retailer = models.CharField(max_length=400) + secondary_offer_url = models.CharField(max_length=400, blank=True, null=True) + secondary_offer_price = models.IntegerField(blank=True, null=True) + rating = models.IntegerField() + is_public = models.BooleanField(default=True) + image = models.FileField(upload_to=get_upload_path, null=True, blank=True) + + class Meta: + ordering = ('-pub_date',) + + def __str__(self): + return self.name + + @property + def get_previous_admin_url(self): + n = self.get_previous_by_read_date() + return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[n.id]) + + @property + def get_next_admin_url(self): + model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name) + try: + return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[self.get_next_by_read_date().pk]) + except model.DoesNotExist: + return '' + + def admin_thumbnail(self): + return format_html('<img src="%s" width="100" style="width:100px" />' % (self.get_image_url())) + admin_thumbnail.short_description = 'Thumbnail' + + def get_full_name(self): + return "%s %s" % (self.brand.name, self.name) + + def get_image_url(self): + return '%sbook-covers/%s' % (settings.IMAGES_URL, self.image.name.split('/')[-1]) + + def save(self, *args, **kwargs): + md = render_images(self.body_markdown) + prods = render_products(md) + self.body_html = markdown_to_html(prods) + super(Product, self).save() + + +@receiver(post_save, sender=Product) +def post_save_events(sender, update_fields, created, instance, **kwargs): + #base_path = "%s/%s/" % (settings.MEDIA_ROOT, "/".join(str(i) for i in instance.image.name.split('/')[:-1])) + #filename, file_extension = os.path.splitext(instance.image.path) + #img = Image.open(instance.image.path) + #resize_image(img, None, 160, 78, base_path, "%s_tn%s" % (filename.split('/')[-1], file_extension)) + #resize_image(img, None, 650, 78, base_path, "%s_small%s" % (filename.split('/')[-1], file_extension)) + pass + diff --git a/app/products/static/product-loader.js b/app/products/static/product-loader.js new file mode 100644 index 0000000..20ec0d6 --- /dev/null +++ b/app/products/static/product-loader.js @@ -0,0 +1,47 @@ +function add_products(){ + var el = document.getElementById("id_body_markdown"); + if (el){ + var iframe = '<iframe frameborder="0" style="border: #dddddd 1px solid;margin-left: 20px;width:330px; height:720px;" src="/luxproduct/insert/?textarea='+el.id+'"></iframe>'; + el.insertAdjacentHTML('afterend', iframe); + } + + var featured_image = document.getElementById("id_featured_image") + + if (featured_image) { + featured_image.querySelectorAll('li').forEach(function(element) { + var cur = element.dataset.imageid; + var loop = Number(element.dataset.loopcounter); + if (cur != "") { + if (loop <= 100) { + console.log(loop); + var request = new XMLHttpRequest(); + request.open('GET', '/photos/luximage/data/admin/tn/'+cur+'/', true); + request.onload = function() { + if (request.status >= 200 && request.status < 400) { + var data = JSON.parse(request.responseText); + var el = element.getElementsByTagName('label')[0]; + url = "url('"+data['url']+"');"; + //console.log(url); + el.style.backgroundImage = 'url('+data["url"]+')'; + + //console.log(el.style); + } else { + console.log("server error", request.statusText); + } + }; + request.onerror = function() { + console.log("error on request"); + }; + request.send(); + } + } + }); + } + +} +document.addEventListener("DOMContentLoaded", function(event) { + add_products(); + md = document.forms["entry_form"].elements["body_markdown"]; + md.style.maxHeight = "300rem"; + md.style.maxWidth = "300rem"; +}); diff --git a/app/products/templates/products/snippet.html b/app/products/templates/products/snippet.html new file mode 100644 index 0000000..be5d874 --- /dev/null +++ b/app/products/templates/products/snippet.html @@ -0,0 +1,26 @@ +<div itemscope itemtype="http://schema.org/Product"> + <meta itemprop="brand" content="{{object.brand.name}}" /> + <h4 class="product-link" itemprop="name">{{object.get_full_name}}: (<span itemprop="offers" itemscope itemtype="http://schema.org/Offer"> + <a href="{{object.primary_offer_url}}" title="buy the {{object.get_full_name}} for ${{object.primary_offer_price}}" itemprop="url" rel="nofollow"> + <span itemprop="priceCurrency" content="USD">$</span><span + itemprop="price" content="{{object.primary_offer_price}}">{{object.primary_offer_price}}</span> {{object.primary_offer_retailer}}</a> + <link itemprop="availability" href="http://schema.org/InStock" /> + </span>) + {% if object.secondary_offer_url %}(<span itemprop="offers" itemscope itemtype="http://schema.org/Offer"> + <a href="{{object.secondary_offer_url}}" title="buy the {{object.get_full_name}} for ${{object.secondary_offer_price}}" itemprop="url"> + <span itemprop="priceCurrency" content="USD">$</span><span + itemprop="price" content="{{object.secondary_offer_price}}">{{object.secondary_offer_price}}</span> {{object.secondary_offer_retailer}}</a> + <link itemprop="availability" href="http://schema.org/InStock" /> + </span>){% endif %}</h4> + <div itemprop="review" itemscope itemtype="http://schema.org/Review"> + <meta itemprop="name" content="{{object.get_full_name}}" /> + <meta itemprop="author" content="Scott Gilbertson" /> + <meta itemprop="datePublished" content="{{object.pub_date}}" /> + <span itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating"> + <meta itemprop="worstRating" content = "1"/> + <meta itemprop="ratingValue" content="{{object.rating}}" /> + <meta itemprop="bestRating" content="10" /> + </span> + <meta itemprop="description" content="{{object.body_markdown}}" /> + </div> +</div> diff --git a/app/products/views.py b/app/products/views.py new file mode 100644 index 0000000..30be542 --- /dev/null +++ b/app/products/views.py @@ -0,0 +1,15 @@ +from django.shortcuts import render +from .models import Product + + +def insert_products(request): + """ + The view that handles the admin insert products feature + """ + object_list = Product.objects.all() + #object_list = sorted( + # chain(images, videos, audio), + # key=lambda instance: instance.pub_date, + # reverse=True + #) + return render(request, 'admin/insert_products.html', {'object_list': object_list, 'textarea_id': request.GET['textarea']}) |